678 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			678 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /*
 | |
| 	MIT License http://www.opensource.org/licenses/mit-license.php
 | |
| 	Author Tobias Koppers @sokra
 | |
| */
 | |
| 
 | |
| "use strict";
 | |
| 
 | |
| // eslint-disable-next-line n/prefer-global/process
 | |
| const { nextTick } = require("process");
 | |
| 
 | |
| /** @typedef {import("./Resolver").FileSystem} FileSystem */
 | |
| /** @typedef {import("./Resolver").PathLike} PathLike */
 | |
| /** @typedef {import("./Resolver").PathOrFileDescriptor} PathOrFileDescriptor */
 | |
| /** @typedef {import("./Resolver").SyncFileSystem} SyncFileSystem */
 | |
| /** @typedef {FileSystem & SyncFileSystem} BaseFileSystem */
 | |
| 
 | |
| /**
 | |
|  * @template T
 | |
|  * @typedef {import("./Resolver").FileSystemCallback<T>} FileSystemCallback<T>
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * @param {string} path path
 | |
|  * @returns {string} dirname
 | |
|  */
 | |
| const dirname = (path) => {
 | |
| 	let idx = path.length - 1;
 | |
| 	while (idx >= 0) {
 | |
| 		const char = path.charCodeAt(idx);
 | |
| 		// slash or backslash
 | |
| 		if (char === 47 || char === 92) break;
 | |
| 		idx--;
 | |
| 	}
 | |
| 	if (idx < 0) return "";
 | |
| 	return path.slice(0, idx);
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * @template T
 | |
|  * @param {FileSystemCallback<T>[]} callbacks callbacks
 | |
|  * @param {Error | null} err error
 | |
|  * @param {T} result result
 | |
|  */
 | |
| const runCallbacks = (callbacks, err, result) => {
 | |
| 	if (callbacks.length === 1) {
 | |
| 		callbacks[0](err, result);
 | |
| 		callbacks.length = 0;
 | |
| 		return;
 | |
| 	}
 | |
| 	let error;
 | |
| 	for (const callback of callbacks) {
 | |
| 		try {
 | |
| 			callback(err, result);
 | |
| 		} catch (err) {
 | |
| 			if (!error) error = err;
 | |
| 		}
 | |
| 	}
 | |
| 	callbacks.length = 0;
 | |
| 	if (error) throw error;
 | |
| };
 | |
| 
 | |
| // eslint-disable-next-line jsdoc/no-restricted-syntax
 | |
| /** @typedef {Function} EXPECTED_FUNCTION */
 | |
| // eslint-disable-next-line jsdoc/no-restricted-syntax
 | |
| /** @typedef {any} EXPECTED_ANY */
 | |
| 
 | |
| class OperationMergerBackend {
 | |
| 	/**
 | |
| 	 * @param {EXPECTED_FUNCTION | undefined} provider async method in filesystem
 | |
| 	 * @param {EXPECTED_FUNCTION | undefined} syncProvider sync method in filesystem
 | |
| 	 * @param {BaseFileSystem} providerContext call context for the provider methods
 | |
| 	 */
 | |
| 	constructor(provider, syncProvider, providerContext) {
 | |
| 		this._provider = provider;
 | |
| 		this._syncProvider = syncProvider;
 | |
| 		this._providerContext = providerContext;
 | |
| 		this._activeAsyncOperations = new Map();
 | |
| 
 | |
| 		this.provide = this._provider
 | |
| 			? // Comment to align jsdoc
 | |
| 				/**
 | |
| 				 * @param {PathLike | PathOrFileDescriptor} path path
 | |
| 				 * @param {object | FileSystemCallback<EXPECTED_ANY> | undefined} options options
 | |
| 				 * @param {FileSystemCallback<EXPECTED_ANY>=} callback callback
 | |
| 				 * @returns {EXPECTED_ANY} result
 | |
| 				 */
 | |
| 				(path, options, callback) => {
 | |
| 					if (typeof options === "function") {
 | |
| 						callback =
 | |
| 							/** @type {FileSystemCallback<EXPECTED_ANY>} */
 | |
| 							(options);
 | |
| 						options = undefined;
 | |
| 					}
 | |
| 					if (
 | |
| 						typeof path !== "string" &&
 | |
| 						!Buffer.isBuffer(path) &&
 | |
| 						!(path instanceof URL) &&
 | |
| 						typeof path !== "number"
 | |
| 					) {
 | |
| 						/** @type {EXPECTED_FUNCTION} */
 | |
| 						(callback)(
 | |
| 							new TypeError("path must be a string, Buffer, URL or number"),
 | |
| 						);
 | |
| 						return;
 | |
| 					}
 | |
| 					if (options) {
 | |
| 						return /** @type {EXPECTED_FUNCTION} */ (this._provider).call(
 | |
| 							this._providerContext,
 | |
| 							path,
 | |
| 							options,
 | |
| 							callback,
 | |
| 						);
 | |
| 					}
 | |
| 					let callbacks = this._activeAsyncOperations.get(path);
 | |
| 					if (callbacks) {
 | |
| 						callbacks.push(callback);
 | |
| 						return;
 | |
| 					}
 | |
| 					this._activeAsyncOperations.set(path, (callbacks = [callback]));
 | |
| 					/** @type {EXPECTED_FUNCTION} */
 | |
| 					(provider)(
 | |
| 						path,
 | |
| 						/**
 | |
| 						 * @param {Error} err error
 | |
| 						 * @param {EXPECTED_ANY} result result
 | |
| 						 */
 | |
| 						(err, result) => {
 | |
| 							this._activeAsyncOperations.delete(path);
 | |
| 							runCallbacks(callbacks, err, result);
 | |
| 						},
 | |
| 					);
 | |
| 				}
 | |
| 			: null;
 | |
| 		this.provideSync = this._syncProvider
 | |
| 			? // Comment to align jsdoc
 | |
| 				/**
 | |
| 				 * @param {PathLike | PathOrFileDescriptor} path path
 | |
| 				 * @param {object=} options options
 | |
| 				 * @returns {EXPECTED_ANY} result
 | |
| 				 */
 | |
| 				(path, options) =>
 | |
| 					/** @type {EXPECTED_FUNCTION} */ (this._syncProvider).call(
 | |
| 						this._providerContext,
 | |
| 						path,
 | |
| 						options,
 | |
| 					)
 | |
| 			: null;
 | |
| 	}
 | |
| 
 | |
| 	purge() {}
 | |
| 
 | |
| 	purgeParent() {}
 | |
| }
 | |
| 
 | |
| /*
 | |
| 
 | |
| IDLE:
 | |
| 	insert data: goto SYNC
 | |
| 
 | |
| SYNC:
 | |
| 	before provide: run ticks
 | |
| 	event loop tick: goto ASYNC_ACTIVE
 | |
| 
 | |
| ASYNC:
 | |
| 	timeout: run tick, goto ASYNC_PASSIVE
 | |
| 
 | |
| ASYNC_PASSIVE:
 | |
| 	before provide: run ticks
 | |
| 
 | |
| IDLE --[insert data]--> SYNC --[event loop tick]--> ASYNC_ACTIVE --[interval tick]-> ASYNC_PASSIVE
 | |
|                                                           ^                             |
 | |
|                                                           +---------[insert data]-------+
 | |
| */
 | |
| 
 | |
| const STORAGE_MODE_IDLE = 0;
 | |
| const STORAGE_MODE_SYNC = 1;
 | |
| const STORAGE_MODE_ASYNC = 2;
 | |
| 
 | |
| /**
 | |
|  * @callback Provide
 | |
|  * @param {PathLike | PathOrFileDescriptor} path path
 | |
|  * @param {EXPECTED_ANY} options options
 | |
|  * @param {FileSystemCallback<EXPECTED_ANY>} callback callback
 | |
|  * @returns {void}
 | |
|  */
 | |
| 
 | |
| class CacheBackend {
 | |
| 	/**
 | |
| 	 * @param {number} duration max cache duration of items
 | |
| 	 * @param {EXPECTED_FUNCTION | undefined} provider async method
 | |
| 	 * @param {EXPECTED_FUNCTION | undefined} syncProvider sync method
 | |
| 	 * @param {BaseFileSystem} providerContext call context for the provider methods
 | |
| 	 */
 | |
| 	constructor(duration, provider, syncProvider, providerContext) {
 | |
| 		this._duration = duration;
 | |
| 		this._provider = provider;
 | |
| 		this._syncProvider = syncProvider;
 | |
| 		this._providerContext = providerContext;
 | |
| 		/** @type {Map<string, FileSystemCallback<EXPECTED_ANY>[]>} */
 | |
| 		this._activeAsyncOperations = new Map();
 | |
| 		/** @type {Map<string, { err: Error | null, result?: EXPECTED_ANY, level: Set<string> }>} */
 | |
| 		this._data = new Map();
 | |
| 		/** @type {Set<string>[]} */
 | |
| 		this._levels = [];
 | |
| 		for (let i = 0; i < 10; i++) this._levels.push(new Set());
 | |
| 		for (let i = 5000; i < duration; i += 500) this._levels.push(new Set());
 | |
| 		this._currentLevel = 0;
 | |
| 		this._tickInterval = Math.floor(duration / this._levels.length);
 | |
| 		/** @type {STORAGE_MODE_IDLE | STORAGE_MODE_SYNC | STORAGE_MODE_ASYNC} */
 | |
| 		this._mode = STORAGE_MODE_IDLE;
 | |
| 
 | |
| 		/** @type {NodeJS.Timeout | undefined} */
 | |
| 		this._timeout = undefined;
 | |
| 		/** @type {number | undefined} */
 | |
| 		this._nextDecay = undefined;
 | |
| 
 | |
| 		// eslint-disable-next-line no-warning-comments
 | |
| 		// @ts-ignore
 | |
| 		this.provide = provider ? this.provide.bind(this) : null;
 | |
| 		// eslint-disable-next-line no-warning-comments
 | |
| 		// @ts-ignore
 | |
| 		this.provideSync = syncProvider ? this.provideSync.bind(this) : null;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * @param {PathLike | PathOrFileDescriptor} path path
 | |
| 	 * @param {EXPECTED_ANY} options options
 | |
| 	 * @param {FileSystemCallback<EXPECTED_ANY>} callback callback
 | |
| 	 * @returns {void}
 | |
| 	 */
 | |
| 	provide(path, options, callback) {
 | |
| 		if (typeof options === "function") {
 | |
| 			callback = options;
 | |
| 			options = undefined;
 | |
| 		}
 | |
| 		if (
 | |
| 			typeof path !== "string" &&
 | |
| 			!Buffer.isBuffer(path) &&
 | |
| 			!(path instanceof URL) &&
 | |
| 			typeof path !== "number"
 | |
| 		) {
 | |
| 			callback(new TypeError("path must be a string, Buffer, URL or number"));
 | |
| 			return;
 | |
| 		}
 | |
| 		const strPath = typeof path !== "string" ? path.toString() : path;
 | |
| 		if (options) {
 | |
| 			return /** @type {EXPECTED_FUNCTION} */ (this._provider).call(
 | |
| 				this._providerContext,
 | |
| 				path,
 | |
| 				options,
 | |
| 				callback,
 | |
| 			);
 | |
| 		}
 | |
| 
 | |
| 		// When in sync mode we can move to async mode
 | |
| 		if (this._mode === STORAGE_MODE_SYNC) {
 | |
| 			this._enterAsyncMode();
 | |
| 		}
 | |
| 
 | |
| 		// Check in cache
 | |
| 		const cacheEntry = this._data.get(strPath);
 | |
| 		if (cacheEntry !== undefined) {
 | |
| 			if (cacheEntry.err) return nextTick(callback, cacheEntry.err);
 | |
| 			return nextTick(callback, null, cacheEntry.result);
 | |
| 		}
 | |
| 
 | |
| 		// Check if there is already the same operation running
 | |
| 		let callbacks = this._activeAsyncOperations.get(strPath);
 | |
| 		if (callbacks !== undefined) {
 | |
| 			callbacks.push(callback);
 | |
| 			return;
 | |
| 		}
 | |
| 		this._activeAsyncOperations.set(strPath, (callbacks = [callback]));
 | |
| 
 | |
| 		// Run the operation
 | |
| 		/** @type {EXPECTED_FUNCTION} */
 | |
| 		(this._provider).call(
 | |
| 			this._providerContext,
 | |
| 			path,
 | |
| 			/**
 | |
| 			 * @param {Error | null} err error
 | |
| 			 * @param {EXPECTED_ANY=} result result
 | |
| 			 */
 | |
| 			(err, result) => {
 | |
| 				this._activeAsyncOperations.delete(strPath);
 | |
| 				this._storeResult(strPath, err, result);
 | |
| 
 | |
| 				// Enter async mode if not yet done
 | |
| 				this._enterAsyncMode();
 | |
| 
 | |
| 				runCallbacks(
 | |
| 					/** @type {FileSystemCallback<EXPECTED_ANY>[]} */ (callbacks),
 | |
| 					err,
 | |
| 					result,
 | |
| 				);
 | |
| 			},
 | |
| 		);
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * @param {PathLike | PathOrFileDescriptor} path path
 | |
| 	 * @param {EXPECTED_ANY} options options
 | |
| 	 * @returns {EXPECTED_ANY} result
 | |
| 	 */
 | |
| 	provideSync(path, options) {
 | |
| 		if (
 | |
| 			typeof path !== "string" &&
 | |
| 			!Buffer.isBuffer(path) &&
 | |
| 			!(path instanceof URL) &&
 | |
| 			typeof path !== "number"
 | |
| 		) {
 | |
| 			throw new TypeError("path must be a string");
 | |
| 		}
 | |
| 		const strPath = typeof path !== "string" ? path.toString() : path;
 | |
| 		if (options) {
 | |
| 			return /** @type {EXPECTED_FUNCTION} */ (this._syncProvider).call(
 | |
| 				this._providerContext,
 | |
| 				path,
 | |
| 				options,
 | |
| 			);
 | |
| 		}
 | |
| 
 | |
| 		// In sync mode we may have to decay some cache items
 | |
| 		if (this._mode === STORAGE_MODE_SYNC) {
 | |
| 			this._runDecays();
 | |
| 		}
 | |
| 
 | |
| 		// Check in cache
 | |
| 		const cacheEntry = this._data.get(strPath);
 | |
| 		if (cacheEntry !== undefined) {
 | |
| 			if (cacheEntry.err) throw cacheEntry.err;
 | |
| 			return cacheEntry.result;
 | |
| 		}
 | |
| 
 | |
| 		// Get all active async operations
 | |
| 		// This sync operation will also complete them
 | |
| 		const callbacks = this._activeAsyncOperations.get(strPath);
 | |
| 		this._activeAsyncOperations.delete(strPath);
 | |
| 
 | |
| 		// Run the operation
 | |
| 		// When in idle mode, we will enter sync mode
 | |
| 		let result;
 | |
| 		try {
 | |
| 			result = /** @type {EXPECTED_FUNCTION} */ (this._syncProvider).call(
 | |
| 				this._providerContext,
 | |
| 				path,
 | |
| 			);
 | |
| 		} catch (err) {
 | |
| 			this._storeResult(strPath, /** @type {Error} */ (err), undefined);
 | |
| 			this._enterSyncModeWhenIdle();
 | |
| 			if (callbacks) {
 | |
| 				runCallbacks(callbacks, /** @type {Error} */ (err), undefined);
 | |
| 			}
 | |
| 			throw err;
 | |
| 		}
 | |
| 		this._storeResult(strPath, null, result);
 | |
| 		this._enterSyncModeWhenIdle();
 | |
| 		if (callbacks) {
 | |
| 			runCallbacks(callbacks, null, result);
 | |
| 		}
 | |
| 		return result;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * @param {(string | Buffer | URL | number | (string | URL | Buffer | number)[] | Set<string | URL | Buffer | number>)=} what what to purge
 | |
| 	 */
 | |
| 	purge(what) {
 | |
| 		if (!what) {
 | |
| 			if (this._mode !== STORAGE_MODE_IDLE) {
 | |
| 				this._data.clear();
 | |
| 				for (const level of this._levels) {
 | |
| 					level.clear();
 | |
| 				}
 | |
| 				this._enterIdleMode();
 | |
| 			}
 | |
| 		} else if (
 | |
| 			typeof what === "string" ||
 | |
| 			Buffer.isBuffer(what) ||
 | |
| 			what instanceof URL ||
 | |
| 			typeof what === "number"
 | |
| 		) {
 | |
| 			const strWhat = typeof what !== "string" ? what.toString() : what;
 | |
| 			for (const [key, data] of this._data) {
 | |
| 				if (key.startsWith(strWhat)) {
 | |
| 					this._data.delete(key);
 | |
| 					data.level.delete(key);
 | |
| 				}
 | |
| 			}
 | |
| 			if (this._data.size === 0) {
 | |
| 				this._enterIdleMode();
 | |
| 			}
 | |
| 		} else {
 | |
| 			for (const [key, data] of this._data) {
 | |
| 				for (const item of what) {
 | |
| 					const strItem = typeof item !== "string" ? item.toString() : item;
 | |
| 					if (key.startsWith(strItem)) {
 | |
| 						this._data.delete(key);
 | |
| 						data.level.delete(key);
 | |
| 						break;
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 			if (this._data.size === 0) {
 | |
| 				this._enterIdleMode();
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * @param {(string | Buffer | URL | number | (string | URL | Buffer | number)[] | Set<string | URL | Buffer | number>)=} what what to purge
 | |
| 	 */
 | |
| 	purgeParent(what) {
 | |
| 		if (!what) {
 | |
| 			this.purge();
 | |
| 		} else if (
 | |
| 			typeof what === "string" ||
 | |
| 			Buffer.isBuffer(what) ||
 | |
| 			what instanceof URL ||
 | |
| 			typeof what === "number"
 | |
| 		) {
 | |
| 			const strWhat = typeof what !== "string" ? what.toString() : what;
 | |
| 			this.purge(dirname(strWhat));
 | |
| 		} else {
 | |
| 			const set = new Set();
 | |
| 			for (const item of what) {
 | |
| 				const strItem = typeof item !== "string" ? item.toString() : item;
 | |
| 				set.add(dirname(strItem));
 | |
| 			}
 | |
| 			this.purge(set);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * @param {string} path path
 | |
| 	 * @param {Error | null} err error
 | |
| 	 * @param {EXPECTED_ANY} result result
 | |
| 	 */
 | |
| 	_storeResult(path, err, result) {
 | |
| 		if (this._data.has(path)) return;
 | |
| 		const level = this._levels[this._currentLevel];
 | |
| 		this._data.set(path, { err, result, level });
 | |
| 		level.add(path);
 | |
| 	}
 | |
| 
 | |
| 	_decayLevel() {
 | |
| 		const nextLevel = (this._currentLevel + 1) % this._levels.length;
 | |
| 		const decay = this._levels[nextLevel];
 | |
| 		this._currentLevel = nextLevel;
 | |
| 		for (const item of decay) {
 | |
| 			this._data.delete(item);
 | |
| 		}
 | |
| 		decay.clear();
 | |
| 		if (this._data.size === 0) {
 | |
| 			this._enterIdleMode();
 | |
| 		} else {
 | |
| 			/** @type {number} */
 | |
| 			(this._nextDecay) += this._tickInterval;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	_runDecays() {
 | |
| 		while (
 | |
| 			/** @type {number} */ (this._nextDecay) <= Date.now() &&
 | |
| 			this._mode !== STORAGE_MODE_IDLE
 | |
| 		) {
 | |
| 			this._decayLevel();
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	_enterAsyncMode() {
 | |
| 		let timeout = 0;
 | |
| 		switch (this._mode) {
 | |
| 			case STORAGE_MODE_ASYNC:
 | |
| 				return;
 | |
| 			case STORAGE_MODE_IDLE:
 | |
| 				this._nextDecay = Date.now() + this._tickInterval;
 | |
| 				timeout = this._tickInterval;
 | |
| 				break;
 | |
| 			case STORAGE_MODE_SYNC:
 | |
| 				this._runDecays();
 | |
| 				// _runDecays may change the mode
 | |
| 				if (
 | |
| 					/** @type {STORAGE_MODE_IDLE | STORAGE_MODE_SYNC | STORAGE_MODE_ASYNC} */
 | |
| 					(this._mode) === STORAGE_MODE_IDLE
 | |
| 				) {
 | |
| 					return;
 | |
| 				}
 | |
| 				timeout = Math.max(
 | |
| 					0,
 | |
| 					/** @type {number} */ (this._nextDecay) - Date.now(),
 | |
| 				);
 | |
| 				break;
 | |
| 		}
 | |
| 		this._mode = STORAGE_MODE_ASYNC;
 | |
| 		const ref = setTimeout(() => {
 | |
| 			this._mode = STORAGE_MODE_SYNC;
 | |
| 			this._runDecays();
 | |
| 		}, timeout);
 | |
| 		if (ref.unref) ref.unref();
 | |
| 		this._timeout = ref;
 | |
| 	}
 | |
| 
 | |
| 	_enterSyncModeWhenIdle() {
 | |
| 		if (this._mode === STORAGE_MODE_IDLE) {
 | |
| 			this._mode = STORAGE_MODE_SYNC;
 | |
| 			this._nextDecay = Date.now() + this._tickInterval;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	_enterIdleMode() {
 | |
| 		this._mode = STORAGE_MODE_IDLE;
 | |
| 		this._nextDecay = undefined;
 | |
| 		if (this._timeout) clearTimeout(this._timeout);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @template {EXPECTED_FUNCTION} Provider
 | |
|  * @template {EXPECTED_FUNCTION} AsyncProvider
 | |
|  * @template FileSystem
 | |
|  * @param {number} duration duration in ms files are cached
 | |
|  * @param {Provider | undefined} provider provider
 | |
|  * @param {AsyncProvider | undefined} syncProvider sync provider
 | |
|  * @param {BaseFileSystem} providerContext provider context
 | |
|  * @returns {OperationMergerBackend | CacheBackend} backend
 | |
|  */
 | |
| const createBackend = (duration, provider, syncProvider, providerContext) => {
 | |
| 	if (duration > 0) {
 | |
| 		return new CacheBackend(duration, provider, syncProvider, providerContext);
 | |
| 	}
 | |
| 	return new OperationMergerBackend(provider, syncProvider, providerContext);
 | |
| };
 | |
| 
 | |
| module.exports = class CachedInputFileSystem {
 | |
| 	/**
 | |
| 	 * @param {BaseFileSystem} fileSystem file system
 | |
| 	 * @param {number} duration duration in ms files are cached
 | |
| 	 */
 | |
| 	constructor(fileSystem, duration) {
 | |
| 		this.fileSystem = fileSystem;
 | |
| 
 | |
| 		this._lstatBackend = createBackend(
 | |
| 			duration,
 | |
| 			this.fileSystem.lstat,
 | |
| 			this.fileSystem.lstatSync,
 | |
| 			this.fileSystem,
 | |
| 		);
 | |
| 		const lstat = this._lstatBackend.provide;
 | |
| 		this.lstat = /** @type {FileSystem["lstat"]} */ (lstat);
 | |
| 		const lstatSync = this._lstatBackend.provideSync;
 | |
| 		this.lstatSync = /** @type {SyncFileSystem["lstatSync"]} */ (lstatSync);
 | |
| 
 | |
| 		this._statBackend = createBackend(
 | |
| 			duration,
 | |
| 			this.fileSystem.stat,
 | |
| 			this.fileSystem.statSync,
 | |
| 			this.fileSystem,
 | |
| 		);
 | |
| 		const stat = this._statBackend.provide;
 | |
| 		this.stat = /** @type {FileSystem["stat"]} */ (stat);
 | |
| 		const statSync = this._statBackend.provideSync;
 | |
| 		this.statSync = /** @type {SyncFileSystem["statSync"]} */ (statSync);
 | |
| 
 | |
| 		this._readdirBackend = createBackend(
 | |
| 			duration,
 | |
| 			this.fileSystem.readdir,
 | |
| 			this.fileSystem.readdirSync,
 | |
| 			this.fileSystem,
 | |
| 		);
 | |
| 		const readdir = this._readdirBackend.provide;
 | |
| 		this.readdir = /** @type {FileSystem["readdir"]} */ (readdir);
 | |
| 		const readdirSync = this._readdirBackend.provideSync;
 | |
| 		this.readdirSync = /** @type {SyncFileSystem["readdirSync"]} */ (
 | |
| 			readdirSync
 | |
| 		);
 | |
| 
 | |
| 		this._readFileBackend = createBackend(
 | |
| 			duration,
 | |
| 			this.fileSystem.readFile,
 | |
| 			this.fileSystem.readFileSync,
 | |
| 			this.fileSystem,
 | |
| 		);
 | |
| 		const readFile = this._readFileBackend.provide;
 | |
| 		this.readFile = /** @type {FileSystem["readFile"]} */ (readFile);
 | |
| 		const readFileSync = this._readFileBackend.provideSync;
 | |
| 		this.readFileSync = /** @type {SyncFileSystem["readFileSync"]} */ (
 | |
| 			readFileSync
 | |
| 		);
 | |
| 
 | |
| 		this._readJsonBackend = createBackend(
 | |
| 			duration,
 | |
| 			// prettier-ignore
 | |
| 			this.fileSystem.readJson ||
 | |
| 				(this.readFile &&
 | |
| 					(
 | |
| 						/**
 | |
| 						 * @param {string} path path
 | |
| 						 * @param {FileSystemCallback<EXPECTED_ANY>} callback callback
 | |
| 						 */
 | |
| 						(path, callback) => {
 | |
| 							this.readFile(path, (err, buffer) => {
 | |
| 								if (err) return callback(err);
 | |
| 								if (!buffer || buffer.length === 0)
 | |
| 									{return callback(new Error("No file content"));}
 | |
| 								let data;
 | |
| 								try {
 | |
| 									data = JSON.parse(buffer.toString("utf8"));
 | |
| 								} catch (err_) {
 | |
| 									return callback(/** @type {Error} */ (err_));
 | |
| 								}
 | |
| 								callback(null, data);
 | |
| 							});
 | |
| 						})
 | |
| 				),
 | |
| 			// prettier-ignore
 | |
| 			this.fileSystem.readJsonSync ||
 | |
| 				(this.readFileSync &&
 | |
| 					(
 | |
| 						/**
 | |
| 						 * @param {string} path path
 | |
| 						 * @returns {EXPECTED_ANY} result
 | |
| 						 */
 | |
| 						(path) => {
 | |
| 							const buffer = this.readFileSync(path);
 | |
| 							const data = JSON.parse(buffer.toString("utf8"));
 | |
| 							return data;
 | |
| 						}
 | |
| 				 )),
 | |
| 			this.fileSystem,
 | |
| 		);
 | |
| 		const readJson = this._readJsonBackend.provide;
 | |
| 		this.readJson = /** @type {FileSystem["readJson"]} */ (readJson);
 | |
| 		const readJsonSync = this._readJsonBackend.provideSync;
 | |
| 		this.readJsonSync = /** @type {SyncFileSystem["readJsonSync"]} */ (
 | |
| 			readJsonSync
 | |
| 		);
 | |
| 
 | |
| 		this._readlinkBackend = createBackend(
 | |
| 			duration,
 | |
| 			this.fileSystem.readlink,
 | |
| 			this.fileSystem.readlinkSync,
 | |
| 			this.fileSystem,
 | |
| 		);
 | |
| 		const readlink = this._readlinkBackend.provide;
 | |
| 		this.readlink = /** @type {FileSystem["readlink"]} */ (readlink);
 | |
| 		const readlinkSync = this._readlinkBackend.provideSync;
 | |
| 		this.readlinkSync = /** @type {SyncFileSystem["readlinkSync"]} */ (
 | |
| 			readlinkSync
 | |
| 		);
 | |
| 
 | |
| 		this._realpathBackend = createBackend(
 | |
| 			duration,
 | |
| 			this.fileSystem.realpath,
 | |
| 			this.fileSystem.realpathSync,
 | |
| 			this.fileSystem,
 | |
| 		);
 | |
| 		const realpath = this._realpathBackend.provide;
 | |
| 		this.realpath = /** @type {FileSystem["realpath"]} */ (realpath);
 | |
| 		const realpathSync = this._realpathBackend.provideSync;
 | |
| 		this.realpathSync = /** @type {SyncFileSystem["realpathSync"]} */ (
 | |
| 			realpathSync
 | |
| 		);
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * @param {(string | Buffer | URL | number | (string | URL | Buffer | number)[] | Set<string | URL | Buffer | number>)=} what what to purge
 | |
| 	 */
 | |
| 	purge(what) {
 | |
| 		this._statBackend.purge(what);
 | |
| 		this._lstatBackend.purge(what);
 | |
| 		this._readdirBackend.purgeParent(what);
 | |
| 		this._readFileBackend.purge(what);
 | |
| 		this._readlinkBackend.purge(what);
 | |
| 		this._readJsonBackend.purge(what);
 | |
| 		this._realpathBackend.purge(what);
 | |
| 	}
 | |
| };
 |