diff --git a/lib/CachedInputFileSystem.js b/lib/CachedInputFileSystem.js index c8d304b..cddf00e 100644 --- a/lib/CachedInputFileSystem.js +++ b/lib/CachedInputFileSystem.js @@ -72,16 +72,28 @@ class OperationMergerBackend { this.provide = this._provider ? /** - * @param {string} path path - * @param {any} options options - * @param {function} callback callback + * @param {PathLike | PathOrFileDescriptor} path path + * @param {object | FileSystemCallback | undefined} options options + * @param {FileSystemCallback=} callback callback * @returns {any} result */ (path, options, callback) => { if (typeof options === "function") { - callback = options; + callback = /** @type {FileSystemCallback} */ (options); options = undefined; } + if ( + typeof path !== "string" && + !Buffer.isBuffer(path) && + !(path instanceof URL) && + typeof path !== "number" + ) { + /** @type {Function} */ + (callback)( + new TypeError("path must be a string, Buffer, URL or number") + ); + return; + } if (options) { return /** @type {Function} */ (this._provider).call( this._providerContext, @@ -90,10 +102,6 @@ class OperationMergerBackend { 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); @@ -116,8 +124,8 @@ class OperationMergerBackend { : null; this.provideSync = this._syncProvider ? /** - * @param {string} path path - * @param {any} options options + * @param {PathLike | PathOrFileDescriptor} path path + * @param {object=} options options * @returns {any} result */ (path, options) => { @@ -213,10 +221,16 @@ class CacheBackend { callback = options; options = undefined; } - if (typeof path !== "string") { - callback(new TypeError("path must be a string")); + 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 {Function} */ (this._provider).call( this._providerContext, @@ -232,19 +246,19 @@ class CacheBackend { } // Check in cache - let cacheEntry = this._data.get(path); + let 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(path); + let callbacks = this._activeAsyncOperations.get(strPath); if (callbacks !== undefined) { callbacks.push(callback); return; } - this._activeAsyncOperations.set(path, (callbacks = [callback])); + this._activeAsyncOperations.set(strPath, (callbacks = [callback])); // Run the operation /** @type {Function} */ @@ -256,8 +270,8 @@ class CacheBackend { * @param {any} [result] result */ (err, result) => { - this._activeAsyncOperations.delete(path); - this._storeResult(path, err, result); + this._activeAsyncOperations.delete(strPath); + this._storeResult(strPath, err, result); // Enter async mode if not yet done this._enterAsyncMode(); @@ -277,9 +291,15 @@ class CacheBackend { * @returns {any} result */ provideSync(path, options) { - if (typeof path !== "string") { + 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 {Function} */ (this._syncProvider).call( this._providerContext, @@ -294,7 +314,7 @@ class CacheBackend { } // Check in cache - let cacheEntry = this._data.get(path); + let cacheEntry = this._data.get(strPath); if (cacheEntry !== undefined) { if (cacheEntry.err) throw cacheEntry.err; return cacheEntry.result; @@ -302,8 +322,8 @@ class CacheBackend { // Get all active async operations // This sync operation will also complete them - const callbacks = this._activeAsyncOperations.get(path); - this._activeAsyncOperations.delete(path); + const callbacks = this._activeAsyncOperations.get(strPath); + this._activeAsyncOperations.delete(strPath); // Run the operation // When in idle mode, we will enter sync mode @@ -314,14 +334,14 @@ class CacheBackend { path ); } catch (err) { - this._storeResult(path, /** @type {Error} */ (err), undefined); + this._storeResult(strPath, /** @type {Error} */ (err), undefined); this._enterSyncModeWhenIdle(); if (callbacks) { runCallbacks(callbacks, /** @type {Error} */ (err), undefined); } throw err; } - this._storeResult(path, null, result); + this._storeResult(strPath, null, result); this._enterSyncModeWhenIdle(); if (callbacks) { runCallbacks(callbacks, null, result); @@ -330,7 +350,7 @@ class CacheBackend { } /** - * @param {string | string[] | Set} [what] what to purge + * @param {string | Buffer | URL | number | (string | URL | Buffer | number)[] | Set} [what] what to purge */ purge(what) { if (!what) { @@ -341,9 +361,15 @@ class CacheBackend { } this._enterIdleMode(); } - } else if (typeof what === "string") { + } else if ( + typeof what === "string" || + Buffer.isBuffer(what) || + what instanceof URL || + typeof what === "number" + ) { + const strWhat = typeof what !== "string" ? what.toString() : what; for (let [key, data] of this._data) { - if (key.startsWith(what)) { + if (key.startsWith(strWhat)) { this._data.delete(key); data.level.delete(key); } @@ -354,7 +380,8 @@ class CacheBackend { } else { for (let [key, data] of this._data) { for (const item of what) { - if (key.startsWith(item)) { + const strItem = typeof item !== "string" ? item.toString() : item; + if (key.startsWith(strItem)) { this._data.delete(key); data.level.delete(key); break; @@ -368,17 +395,24 @@ class CacheBackend { } /** - * @param {string|string[]|Set} [what] what to purge + * @param {string | Buffer | URL | number | (string | URL | Buffer | number)[] | Set} [what] what to purge */ purgeParent(what) { if (!what) { this.purge(); - } else if (typeof what === "string") { - this.purge(dirname(what)); + } 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) { - set.add(dirname(item)); + const strItem = typeof item !== "string" ? item.toString() : item; + set.add(dirname(strItem)); } this.purge(set); } @@ -616,7 +650,7 @@ module.exports = class CachedInputFileSystem { } /** - * @param {string|string[]|Set} [what] what to purge + * @param {string | Buffer | URL | number | (string | URL | Buffer | number)[] | Set} [what] what to purge */ purge(what) { this._statBackend.purge(what); diff --git a/test/CachedInputFileSystem.test.js b/test/CachedInputFileSystem.test.js index 2ee1d7c..c3a70e1 100644 --- a/test/CachedInputFileSystem.test.js +++ b/test/CachedInputFileSystem.test.js @@ -1,4 +1,6 @@ const { CachedInputFileSystem } = require("../"); +const path = require("path"); +const url = require("url"); describe("CachedInputFileSystem OperationMergerBackend ('stat' and 'statSync')", () => { let fs; @@ -432,7 +434,19 @@ describe("CachedInputFileSystem CacheBackend", () => { fs.purge(["/test/path"]); fs.readdir("/test/path", (err, r) => { expect(r[0]).toEqual("2"); - done(); + fs.purge([url.pathToFileURL("/test/path")]); + fs.readdir("/test/path", (err, r) => { + expect(r[0]).toEqual("2"); + fs.purge(Buffer.from("/test/path")); + fs.readdir("/test/path", (err, r) => { + expect(r[0]).toEqual("3"); + fs.purge([Buffer.from("/test/path")]); + fs.readdir("/test/path", (err, r) => { + expect(r[0]).toEqual("4"); + done(); + }); + }); + }); }); }); }); @@ -453,3 +467,119 @@ describe("CachedInputFileSystem CacheBackend", () => { next(); }); }); + +describe("CachedInputFileSystem CacheBackend and Node.JS filesystem", () => { + let fs; + + beforeEach(() => { + fs = new CachedInputFileSystem(require("fs"), 1); + }); + + const file = path.resolve(__dirname, "./fixtures/abc.txt"); + + it("should work with string async", function (done) { + fs.readFile(file, (err, r) => { + if (err) { + done(err); + return; + } + expect(r.toString()).toEqual("abc"); + done(); + }); + }); + + it("should work with string sync", function () { + const r = fs.readFileSync(file); + expect(r.toString()).toEqual("abc"); + }); + + it("should work with Buffer async", function (done) { + fs.readFile(Buffer.from(file), (err, r) => { + if (err) { + done(err); + return; + } + expect(r.toString()).toEqual("abc"); + done(); + }); + }); + + it("should work with Buffer sync", function () { + const r = fs.readFileSync(Buffer.from(file)); + expect(r.toString()).toEqual("abc"); + }); + + it("should work with URL async", function (done) { + fs.readFile(url.pathToFileURL(file), (err, r) => { + if (err) { + done(err); + return; + } + expect(r.toString()).toEqual("abc"); + done(); + }); + }); + + it("should work with URL sync", function () { + const r = fs.readFileSync(url.pathToFileURL(file)); + expect(r.toString()).toEqual("abc"); + }); +}); + +describe("CachedInputFileSystem OperationMergerBackend and Node.JS filesystem", () => { + let fs; + + beforeEach(() => { + fs = new CachedInputFileSystem(require("fs"), 0); + }); + + const file = path.resolve(__dirname, "./fixtures/abc.txt"); + + it("should work with string async", function (done) { + fs.readFile(file, (err, r) => { + if (err) { + done(err); + return; + } + expect(r.toString()).toEqual("abc"); + done(); + }); + }); + + it("should work with string sync", function () { + const r = fs.readFileSync(file); + expect(r.toString()).toEqual("abc"); + }); + + it("should work with Buffer async", function (done) { + fs.readFile(Buffer.from(file), (err, r) => { + if (err) { + done(err); + return; + } + expect(r.toString()).toEqual("abc"); + done(); + }); + }); + + it("should work with Buffer sync", function () { + const r = fs.readFileSync(Buffer.from(file)); + expect(r.toString()).toEqual("abc"); + }); + + it("should work with URL async", function (done) { + fs.readFile(url.pathToFileURL(file), (err, r) => { + if (err) { + done(err); + return; + } + expect(r.toString()).toEqual("abc"); + done(); + }); + }); + + it("should work with URL sync", function () { + const r = fs.readFileSync(url.pathToFileURL(file)); + expect(r.toString()).toEqual("abc"); + }); +}); diff --git a/types.d.ts b/types.d.ts index 6c69f4f..d3bf8bb 100644 --- a/types.d.ts +++ b/types.d.ts @@ -75,7 +75,15 @@ declare class CachedInputFileSystem { readlinkSync: ReadlinkSync; realpath?: RealPath; realpathSync?: RealPathSync; - purge(what?: string | Set | string[]): void; + purge( + what?: + | string + | number + | Buffer + | URL_url + | (string | number | Buffer | URL_url)[] + | Set + ): void; } declare class CloneBasenamePlugin { constructor(