Skip to content

Commit 834875a

Browse files
fix: support Buffer/URL/number paths in cached filessystem
2 parents 2f51fb0 + eb55873 commit 834875a

File tree

3 files changed

+206
-34
lines changed

3 files changed

+206
-34
lines changed

lib/CachedInputFileSystem.js

+66-32
Original file line numberDiff line numberDiff line change
@@ -72,16 +72,28 @@ class OperationMergerBackend {
7272

7373
this.provide = this._provider
7474
? /**
75-
* @param {string} path path
76-
* @param {any} options options
77-
* @param {function} callback callback
75+
* @param {PathLike | PathOrFileDescriptor} path path
76+
* @param {object | FileSystemCallback<any> | undefined} options options
77+
* @param {FileSystemCallback<any>=} callback callback
7878
* @returns {any} result
7979
*/
8080
(path, options, callback) => {
8181
if (typeof options === "function") {
82-
callback = options;
82+
callback = /** @type {FileSystemCallback<any>} */ (options);
8383
options = undefined;
8484
}
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+
}
8597
if (options) {
8698
return /** @type {Function} */ (this._provider).call(
8799
this._providerContext,
@@ -90,10 +102,6 @@ class OperationMergerBackend {
90102
callback
91103
);
92104
}
93-
if (typeof path !== "string") {
94-
callback(new TypeError("path must be a string"));
95-
return;
96-
}
97105
let callbacks = this._activeAsyncOperations.get(path);
98106
if (callbacks) {
99107
callbacks.push(callback);
@@ -116,8 +124,8 @@ class OperationMergerBackend {
116124
: null;
117125
this.provideSync = this._syncProvider
118126
? /**
119-
* @param {string} path path
120-
* @param {any} options options
127+
* @param {PathLike | PathOrFileDescriptor} path path
128+
* @param {object=} options options
121129
* @returns {any} result
122130
*/
123131
(path, options) => {
@@ -213,10 +221,16 @@ class CacheBackend {
213221
callback = options;
214222
options = undefined;
215223
}
216-
if (typeof path !== "string") {
217-
callback(new TypeError("path must be a string"));
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"));
218231
return;
219232
}
233+
const strPath = typeof path !== "string" ? path.toString() : path;
220234
if (options) {
221235
return /** @type {Function} */ (this._provider).call(
222236
this._providerContext,
@@ -232,19 +246,19 @@ class CacheBackend {
232246
}
233247

234248
// Check in cache
235-
let cacheEntry = this._data.get(path);
249+
let cacheEntry = this._data.get(strPath);
236250
if (cacheEntry !== undefined) {
237251
if (cacheEntry.err) return nextTick(callback, cacheEntry.err);
238252
return nextTick(callback, null, cacheEntry.result);
239253
}
240254

241255
// Check if there is already the same operation running
242-
let callbacks = this._activeAsyncOperations.get(path);
256+
let callbacks = this._activeAsyncOperations.get(strPath);
243257
if (callbacks !== undefined) {
244258
callbacks.push(callback);
245259
return;
246260
}
247-
this._activeAsyncOperations.set(path, (callbacks = [callback]));
261+
this._activeAsyncOperations.set(strPath, (callbacks = [callback]));
248262

249263
// Run the operation
250264
/** @type {Function} */
@@ -256,8 +270,8 @@ class CacheBackend {
256270
* @param {any} [result] result
257271
*/
258272
(err, result) => {
259-
this._activeAsyncOperations.delete(path);
260-
this._storeResult(path, err, result);
273+
this._activeAsyncOperations.delete(strPath);
274+
this._storeResult(strPath, err, result);
261275

262276
// Enter async mode if not yet done
263277
this._enterAsyncMode();
@@ -277,9 +291,15 @@ class CacheBackend {
277291
* @returns {any} result
278292
*/
279293
provideSync(path, options) {
280-
if (typeof path !== "string") {
294+
if (
295+
typeof path !== "string" &&
296+
!Buffer.isBuffer(path) &&
297+
!(path instanceof URL) &&
298+
typeof path !== "number"
299+
) {
281300
throw new TypeError("path must be a string");
282301
}
302+
const strPath = typeof path !== "string" ? path.toString() : path;
283303
if (options) {
284304
return /** @type {Function} */ (this._syncProvider).call(
285305
this._providerContext,
@@ -294,16 +314,16 @@ class CacheBackend {
294314
}
295315

296316
// Check in cache
297-
let cacheEntry = this._data.get(path);
317+
let cacheEntry = this._data.get(strPath);
298318
if (cacheEntry !== undefined) {
299319
if (cacheEntry.err) throw cacheEntry.err;
300320
return cacheEntry.result;
301321
}
302322

303323
// Get all active async operations
304324
// This sync operation will also complete them
305-
const callbacks = this._activeAsyncOperations.get(path);
306-
this._activeAsyncOperations.delete(path);
325+
const callbacks = this._activeAsyncOperations.get(strPath);
326+
this._activeAsyncOperations.delete(strPath);
307327

308328
// Run the operation
309329
// When in idle mode, we will enter sync mode
@@ -314,14 +334,14 @@ class CacheBackend {
314334
path
315335
);
316336
} catch (err) {
317-
this._storeResult(path, /** @type {Error} */ (err), undefined);
337+
this._storeResult(strPath, /** @type {Error} */ (err), undefined);
318338
this._enterSyncModeWhenIdle();
319339
if (callbacks) {
320340
runCallbacks(callbacks, /** @type {Error} */ (err), undefined);
321341
}
322342
throw err;
323343
}
324-
this._storeResult(path, null, result);
344+
this._storeResult(strPath, null, result);
325345
this._enterSyncModeWhenIdle();
326346
if (callbacks) {
327347
runCallbacks(callbacks, null, result);
@@ -330,7 +350,7 @@ class CacheBackend {
330350
}
331351

332352
/**
333-
* @param {string | string[] | Set<string>} [what] what to purge
353+
* @param {string | Buffer | URL | number | (string | URL | Buffer | number)[] | Set<string | URL | Buffer | number>} [what] what to purge
334354
*/
335355
purge(what) {
336356
if (!what) {
@@ -341,9 +361,15 @@ class CacheBackend {
341361
}
342362
this._enterIdleMode();
343363
}
344-
} else if (typeof what === "string") {
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;
345371
for (let [key, data] of this._data) {
346-
if (key.startsWith(what)) {
372+
if (key.startsWith(strWhat)) {
347373
this._data.delete(key);
348374
data.level.delete(key);
349375
}
@@ -354,7 +380,8 @@ class CacheBackend {
354380
} else {
355381
for (let [key, data] of this._data) {
356382
for (const item of what) {
357-
if (key.startsWith(item)) {
383+
const strItem = typeof item !== "string" ? item.toString() : item;
384+
if (key.startsWith(strItem)) {
358385
this._data.delete(key);
359386
data.level.delete(key);
360387
break;
@@ -368,17 +395,24 @@ class CacheBackend {
368395
}
369396

370397
/**
371-
* @param {string|string[]|Set<string>} [what] what to purge
398+
* @param {string | Buffer | URL | number | (string | URL | Buffer | number)[] | Set<string | URL | Buffer | number>} [what] what to purge
372399
*/
373400
purgeParent(what) {
374401
if (!what) {
375402
this.purge();
376-
} else if (typeof what === "string") {
377-
this.purge(dirname(what));
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));
378411
} else {
379412
const set = new Set();
380413
for (const item of what) {
381-
set.add(dirname(item));
414+
const strItem = typeof item !== "string" ? item.toString() : item;
415+
set.add(dirname(strItem));
382416
}
383417
this.purge(set);
384418
}
@@ -616,7 +650,7 @@ module.exports = class CachedInputFileSystem {
616650
}
617651

618652
/**
619-
* @param {string|string[]|Set<string>} [what] what to purge
653+
* @param {string | Buffer | URL | number | (string | URL | Buffer | number)[] | Set<string | URL | Buffer | number>} [what] what to purge
620654
*/
621655
purge(what) {
622656
this._statBackend.purge(what);

test/CachedInputFileSystem.test.js

+131-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
const { CachedInputFileSystem } = require("../");
2+
const path = require("path");
3+
const url = require("url");
24

35
describe("CachedInputFileSystem OperationMergerBackend ('stat' and 'statSync')", () => {
46
let fs;
@@ -432,7 +434,19 @@ describe("CachedInputFileSystem CacheBackend", () => {
432434
fs.purge(["/test/path"]);
433435
fs.readdir("/test/path", (err, r) => {
434436
expect(r[0]).toEqual("2");
435-
done();
437+
fs.purge([url.pathToFileURL("/test/path")]);
438+
fs.readdir("/test/path", (err, r) => {
439+
expect(r[0]).toEqual("2");
440+
fs.purge(Buffer.from("/test/path"));
441+
fs.readdir("/test/path", (err, r) => {
442+
expect(r[0]).toEqual("3");
443+
fs.purge([Buffer.from("/test/path")]);
444+
fs.readdir("/test/path", (err, r) => {
445+
expect(r[0]).toEqual("4");
446+
done();
447+
});
448+
});
449+
});
436450
});
437451
});
438452
});
@@ -453,3 +467,119 @@ describe("CachedInputFileSystem CacheBackend", () => {
453467
next();
454468
});
455469
});
470+
471+
describe("CachedInputFileSystem CacheBackend and Node.JS filesystem", () => {
472+
let fs;
473+
474+
beforeEach(() => {
475+
fs = new CachedInputFileSystem(require("fs"), 1);
476+
});
477+
478+
const file = path.resolve(__dirname, "./fixtures/abc.txt");
479+
480+
it("should work with string async", function (done) {
481+
fs.readFile(file, (err, r) => {
482+
if (err) {
483+
done(err);
484+
return;
485+
}
486+
expect(r.toString()).toEqual("abc");
487+
done();
488+
});
489+
});
490+
491+
it("should work with string sync", function () {
492+
const r = fs.readFileSync(file);
493+
expect(r.toString()).toEqual("abc");
494+
});
495+
496+
it("should work with Buffer async", function (done) {
497+
fs.readFile(Buffer.from(file), (err, r) => {
498+
if (err) {
499+
done(err);
500+
return;
501+
}
502+
expect(r.toString()).toEqual("abc");
503+
done();
504+
});
505+
});
506+
507+
it("should work with Buffer sync", function () {
508+
const r = fs.readFileSync(Buffer.from(file));
509+
expect(r.toString()).toEqual("abc");
510+
});
511+
512+
it("should work with URL async", function (done) {
513+
fs.readFile(url.pathToFileURL(file), (err, r) => {
514+
if (err) {
515+
done(err);
516+
return;
517+
}
518+
expect(r.toString()).toEqual("abc");
519+
done();
520+
});
521+
});
522+
523+
it("should work with URL sync", function () {
524+
const r = fs.readFileSync(url.pathToFileURL(file));
525+
expect(r.toString()).toEqual("abc");
526+
});
527+
});
528+
529+
describe("CachedInputFileSystem OperationMergerBackend and Node.JS filesystem", () => {
530+
let fs;
531+
532+
beforeEach(() => {
533+
fs = new CachedInputFileSystem(require("fs"), 0);
534+
});
535+
536+
const file = path.resolve(__dirname, "./fixtures/abc.txt");
537+
538+
it("should work with string async", function (done) {
539+
fs.readFile(file, (err, r) => {
540+
if (err) {
541+
done(err);
542+
return;
543+
}
544+
expect(r.toString()).toEqual("abc");
545+
done();
546+
});
547+
});
548+
549+
it("should work with string sync", function () {
550+
const r = fs.readFileSync(file);
551+
expect(r.toString()).toEqual("abc");
552+
});
553+
554+
it("should work with Buffer async", function (done) {
555+
fs.readFile(Buffer.from(file), (err, r) => {
556+
if (err) {
557+
done(err);
558+
return;
559+
}
560+
expect(r.toString()).toEqual("abc");
561+
done();
562+
});
563+
});
564+
565+
it("should work with Buffer sync", function () {
566+
const r = fs.readFileSync(Buffer.from(file));
567+
expect(r.toString()).toEqual("abc");
568+
});
569+
570+
it("should work with URL async", function (done) {
571+
fs.readFile(url.pathToFileURL(file), (err, r) => {
572+
if (err) {
573+
done(err);
574+
return;
575+
}
576+
expect(r.toString()).toEqual("abc");
577+
done();
578+
});
579+
});
580+
581+
it("should work with URL sync", function () {
582+
const r = fs.readFileSync(url.pathToFileURL(file));
583+
expect(r.toString()).toEqual("abc");
584+
});
585+
});

0 commit comments

Comments
 (0)