source: frontend/node_modules/enhanced-resolve/lib/CachedInputFileSystem.js@ 9af201e

Last change on this file since 9af201e was 9af201e, checked in by MBK <marija.karapandzova@…>, 12 days ago

Fix frontend appearance

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