aboutsummaryrefslogtreecommitdiff
path: root/node_modules/enhanced-resolve/lib/CachedInputFileSystem.js
diff options
context:
space:
mode:
authorMinteck <nekostarfan@gmail.com>2021-08-24 14:41:48 +0200
committerMinteck <nekostarfan@gmail.com>2021-08-24 14:41:48 +0200
commitd25e11bee6ca5ca523884da132d18e1400e077b9 (patch)
tree8af39fde19f7ed640a60fb397c7edd647dff1c4c /node_modules/enhanced-resolve/lib/CachedInputFileSystem.js
downloadkartik-iridium-d25e11bee6ca5ca523884da132d18e1400e077b9.tar.gz
kartik-iridium-d25e11bee6ca5ca523884da132d18e1400e077b9.tar.bz2
kartik-iridium-d25e11bee6ca5ca523884da132d18e1400e077b9.zip
Initial commit
Diffstat (limited to 'node_modules/enhanced-resolve/lib/CachedInputFileSystem.js')
-rw-r--r--node_modules/enhanced-resolve/lib/CachedInputFileSystem.js477
1 files changed, 477 insertions, 0 deletions
diff --git a/node_modules/enhanced-resolve/lib/CachedInputFileSystem.js b/node_modules/enhanced-resolve/lib/CachedInputFileSystem.js
new file mode 100644
index 0000000..74dbd98
--- /dev/null
+++ b/node_modules/enhanced-resolve/lib/CachedInputFileSystem.js
@@ -0,0 +1,477 @@
+/*
+ MIT License http://www.opensource.org/licenses/mit-license.php
+ Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const nextTick = require("process").nextTick;
+
+/** @typedef {import("./Resolver").FileSystem} FileSystem */
+/** @typedef {import("./Resolver").SyncFileSystem} SyncFileSystem */
+
+const dirname = path => {
+ let idx = path.length - 1;
+ while (idx >= 0) {
+ const c = path.charCodeAt(idx);
+ // slash or backslash
+ if (c === 47 || c === 92) break;
+ idx--;
+ }
+ if (idx < 0) return "";
+ return path.slice(0, idx);
+};
+
+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 (e) {
+ if (!error) error = e;
+ }
+ }
+ callbacks.length = 0;
+ if (error) throw error;
+};
+
+class OperationMergerBackend {
+ /**
+ * @param {any} provider async method
+ * @param {any} syncProvider sync method
+ * @param {any} 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
+ ? (path, options, callback) => {
+ if (typeof options === "function") {
+ callback = options;
+ options = undefined;
+ }
+ if (options) {
+ return this._provider.call(
+ this._providerContext,
+ path,
+ options,
+ callback
+ );
+ }
+ if (typeof path !== "string") {
+ callback(new TypeError("path must be a string"));
+ return;
+ }
+ let callbacks = this._activeAsyncOperations.get(path);
+ if (callbacks) {
+ callbacks.push(callback);
+ return;
+ }
+ this._activeAsyncOperations.set(path, (callbacks = [callback]));
+ provider(path, (err, result) => {
+ this._activeAsyncOperations.delete(path);
+ runCallbacks(callbacks, err, result);
+ });
+ }
+ : null;
+ this.provideSync = this._syncProvider
+ ? (path, options) => {
+ return 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;
+
+class CacheBackend {
+ /**
+ * @param {number} duration max cache duration of items
+ * @param {any} provider async method
+ * @param {any} syncProvider sync method
+ * @param {any} 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, (function(Error, any): void)[]>} */
+ this._activeAsyncOperations = new Map();
+ /** @type {Map<string, { err: Error, result: 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;
+
+ this.provide = provider ? this.provide.bind(this) : null;
+ this.provideSync = syncProvider ? this.provideSync.bind(this) : null;
+ }
+
+ provide(path, options, callback) {
+ if (typeof options === "function") {
+ callback = options;
+ options = undefined;
+ }
+ if (typeof path !== "string") {
+ callback(new TypeError("path must be a string"));
+ return;
+ }
+ if (options) {
+ return 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
+ let cacheEntry = this._data.get(path);
+ 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(path);
+ if (callbacks !== undefined) {
+ callbacks.push(callback);
+ return;
+ }
+ this._activeAsyncOperations.set(path, (callbacks = [callback]));
+
+ // Run the operation
+ this._provider.call(this._providerContext, path, (err, result) => {
+ this._activeAsyncOperations.delete(path);
+ this._storeResult(path, err, result);
+
+ // Enter async mode if not yet done
+ this._enterAsyncMode();
+
+ runCallbacks(callbacks, err, result);
+ });
+ }
+
+ provideSync(path, options) {
+ if (typeof path !== "string") {
+ throw new TypeError("path must be a string");
+ }
+ if (options) {
+ return 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
+ let cacheEntry = this._data.get(path);
+ 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(path);
+ this._activeAsyncOperations.delete(path);
+
+ // Run the operation
+ // When in idle mode, we will enter sync mode
+ let result;
+ try {
+ result = this._syncProvider.call(this._providerContext, path);
+ } catch (err) {
+ this._storeResult(path, err, undefined);
+ this._enterSyncModeWhenIdle();
+ if (callbacks) runCallbacks(callbacks, err, undefined);
+ throw err;
+ }
+ this._storeResult(path, undefined, result);
+ this._enterSyncModeWhenIdle();
+ if (callbacks) runCallbacks(callbacks, undefined, result);
+ return result;
+ }
+
+ 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") {
+ for (let [key, data] of this._data) {
+ if (key.startsWith(what)) {
+ this._data.delete(key);
+ data.level.delete(key);
+ }
+ }
+ if (this._data.size === 0) {
+ this._enterIdleMode();
+ }
+ } else {
+ for (let [key, data] of this._data) {
+ for (const item of what) {
+ if (key.startsWith(item)) {
+ this._data.delete(key);
+ data.level.delete(key);
+ break;
+ }
+ }
+ }
+ if (this._data.size === 0) {
+ this._enterIdleMode();
+ }
+ }
+ }
+
+ purgeParent(what) {
+ if (!what) {
+ this.purge();
+ } else if (typeof what === "string") {
+ this.purge(dirname(what));
+ } else {
+ const set = new Set();
+ for (const item of what) {
+ set.add(dirname(item));
+ }
+ this.purge(set);
+ }
+ }
+
+ _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 (let item of decay) {
+ this._data.delete(item);
+ }
+ decay.clear();
+ if (this._data.size === 0) {
+ this._enterIdleMode();
+ } else {
+ // @ts-ignore _nextDecay is always a number in sync mode
+ 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();
+ // @ts-ignore _runDecays may change the mode
+ if (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);
+ }
+}
+
+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 {
+ 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,
+ this.fileSystem.readJson ||
+ (this.readFile &&
+ ((path, callback) => {
+ // @ts-ignore
+ 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("utf-8"));
+ } catch (e) {
+ return callback(e);
+ }
+ callback(null, data);
+ });
+ })),
+ this.fileSystem.readJsonSync ||
+ (this.readFileSync &&
+ (path => {
+ const buffer = this.readFileSync(path);
+ const data = JSON.parse(buffer.toString("utf-8"));
+ 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);
+ }
+
+ 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);
+ }
+};