source: imaps-frontend/node_modules/enhanced-resolve/lib/CachedInputFileSystem.js

main
Last change on this file was 79a0317, checked in by stefan toskovski <stefantoska84@…>, 4 days ago

F4 Finalna Verzija

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