source: trip-planner-front/node_modules/enhanced-resolve/lib/CachedInputFileSystem.js

Last change on this file was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 12.6 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").SyncFileSystem} SyncFileSystem */
12
13const dirname = path => {
14 let idx = path.length - 1;
15 while (idx >= 0) {
16 const c = path.charCodeAt(idx);
17 // slash or backslash
18 if (c === 47 || c === 92) break;
19 idx--;
20 }
21 if (idx < 0) return "";
22 return path.slice(0, idx);
23};
24
25const runCallbacks = (callbacks, err, result) => {
26 if (callbacks.length === 1) {
27 callbacks[0](err, result);
28 callbacks.length = 0;
29 return;
30 }
31 let error;
32 for (const callback of callbacks) {
33 try {
34 callback(err, result);
35 } catch (e) {
36 if (!error) error = e;
37 }
38 }
39 callbacks.length = 0;
40 if (error) throw error;
41};
42
43class OperationMergerBackend {
44 /**
45 * @param {any} provider async method
46 * @param {any} syncProvider sync method
47 * @param {any} providerContext call context for the provider methods
48 */
49 constructor(provider, syncProvider, providerContext) {
50 this._provider = provider;
51 this._syncProvider = syncProvider;
52 this._providerContext = providerContext;
53 this._activeAsyncOperations = new Map();
54
55 this.provide = this._provider
56 ? (path, options, callback) => {
57 if (typeof options === "function") {
58 callback = options;
59 options = undefined;
60 }
61 if (options) {
62 return this._provider.call(
63 this._providerContext,
64 path,
65 options,
66 callback
67 );
68 }
69 if (typeof path !== "string") {
70 callback(new TypeError("path must be a string"));
71 return;
72 }
73 let callbacks = this._activeAsyncOperations.get(path);
74 if (callbacks) {
75 callbacks.push(callback);
76 return;
77 }
78 this._activeAsyncOperations.set(path, (callbacks = [callback]));
79 provider(path, (err, result) => {
80 this._activeAsyncOperations.delete(path);
81 runCallbacks(callbacks, err, result);
82 });
83 }
84 : null;
85 this.provideSync = this._syncProvider
86 ? (path, options) => {
87 return this._syncProvider.call(this._providerContext, path, options);
88 }
89 : null;
90 }
91
92 purge() {}
93 purgeParent() {}
94}
95
96/*
97
98IDLE:
99 insert data: goto SYNC
100
101SYNC:
102 before provide: run ticks
103 event loop tick: goto ASYNC_ACTIVE
104
105ASYNC:
106 timeout: run tick, goto ASYNC_PASSIVE
107
108ASYNC_PASSIVE:
109 before provide: run ticks
110
111IDLE --[insert data]--> SYNC --[event loop tick]--> ASYNC_ACTIVE --[interval tick]-> ASYNC_PASSIVE
112 ^ |
113 +---------[insert data]-------+
114*/
115
116const STORAGE_MODE_IDLE = 0;
117const STORAGE_MODE_SYNC = 1;
118const STORAGE_MODE_ASYNC = 2;
119
120class CacheBackend {
121 /**
122 * @param {number} duration max cache duration of items
123 * @param {any} provider async method
124 * @param {any} syncProvider sync method
125 * @param {any} providerContext call context for the provider methods
126 */
127 constructor(duration, provider, syncProvider, providerContext) {
128 this._duration = duration;
129 this._provider = provider;
130 this._syncProvider = syncProvider;
131 this._providerContext = providerContext;
132 /** @type {Map<string, (function(Error, any): void)[]>} */
133 this._activeAsyncOperations = new Map();
134 /** @type {Map<string, { err: Error, result: any, level: Set<string> }>} */
135 this._data = new Map();
136 /** @type {Set<string>[]} */
137 this._levels = [];
138 for (let i = 0; i < 10; i++) this._levels.push(new Set());
139 for (let i = 5000; i < duration; i += 500) this._levels.push(new Set());
140 this._currentLevel = 0;
141 this._tickInterval = Math.floor(duration / this._levels.length);
142 /** @type {STORAGE_MODE_IDLE | STORAGE_MODE_SYNC | STORAGE_MODE_ASYNC} */
143 this._mode = STORAGE_MODE_IDLE;
144
145 /** @type {NodeJS.Timeout | undefined} */
146 this._timeout = undefined;
147 /** @type {number | undefined} */
148 this._nextDecay = undefined;
149
150 this.provide = provider ? this.provide.bind(this) : null;
151 this.provideSync = syncProvider ? this.provideSync.bind(this) : null;
152 }
153
154 provide(path, options, callback) {
155 if (typeof options === "function") {
156 callback = options;
157 options = undefined;
158 }
159 if (typeof path !== "string") {
160 callback(new TypeError("path must be a string"));
161 return;
162 }
163 if (options) {
164 return this._provider.call(
165 this._providerContext,
166 path,
167 options,
168 callback
169 );
170 }
171
172 // When in sync mode we can move to async mode
173 if (this._mode === STORAGE_MODE_SYNC) {
174 this._enterAsyncMode();
175 }
176
177 // Check in cache
178 let cacheEntry = this._data.get(path);
179 if (cacheEntry !== undefined) {
180 if (cacheEntry.err) return nextTick(callback, cacheEntry.err);
181 return nextTick(callback, null, cacheEntry.result);
182 }
183
184 // Check if there is already the same operation running
185 let callbacks = this._activeAsyncOperations.get(path);
186 if (callbacks !== undefined) {
187 callbacks.push(callback);
188 return;
189 }
190 this._activeAsyncOperations.set(path, (callbacks = [callback]));
191
192 // Run the operation
193 this._provider.call(this._providerContext, path, (err, result) => {
194 this._activeAsyncOperations.delete(path);
195 this._storeResult(path, err, result);
196
197 // Enter async mode if not yet done
198 this._enterAsyncMode();
199
200 runCallbacks(callbacks, err, result);
201 });
202 }
203
204 provideSync(path, options) {
205 if (typeof path !== "string") {
206 throw new TypeError("path must be a string");
207 }
208 if (options) {
209 return this._syncProvider.call(this._providerContext, path, options);
210 }
211
212 // In sync mode we may have to decay some cache items
213 if (this._mode === STORAGE_MODE_SYNC) {
214 this._runDecays();
215 }
216
217 // Check in cache
218 let cacheEntry = this._data.get(path);
219 if (cacheEntry !== undefined) {
220 if (cacheEntry.err) throw cacheEntry.err;
221 return cacheEntry.result;
222 }
223
224 // Get all active async operations
225 // This sync operation will also complete them
226 const callbacks = this._activeAsyncOperations.get(path);
227 this._activeAsyncOperations.delete(path);
228
229 // Run the operation
230 // When in idle mode, we will enter sync mode
231 let result;
232 try {
233 result = this._syncProvider.call(this._providerContext, path);
234 } catch (err) {
235 this._storeResult(path, err, undefined);
236 this._enterSyncModeWhenIdle();
237 if (callbacks) runCallbacks(callbacks, err, undefined);
238 throw err;
239 }
240 this._storeResult(path, undefined, result);
241 this._enterSyncModeWhenIdle();
242 if (callbacks) runCallbacks(callbacks, undefined, result);
243 return result;
244 }
245
246 purge(what) {
247 if (!what) {
248 if (this._mode !== STORAGE_MODE_IDLE) {
249 this._data.clear();
250 for (const level of this._levels) {
251 level.clear();
252 }
253 this._enterIdleMode();
254 }
255 } else if (typeof what === "string") {
256 for (let [key, data] of this._data) {
257 if (key.startsWith(what)) {
258 this._data.delete(key);
259 data.level.delete(key);
260 }
261 }
262 if (this._data.size === 0) {
263 this._enterIdleMode();
264 }
265 } else {
266 for (let [key, data] of this._data) {
267 for (const item of what) {
268 if (key.startsWith(item)) {
269 this._data.delete(key);
270 data.level.delete(key);
271 break;
272 }
273 }
274 }
275 if (this._data.size === 0) {
276 this._enterIdleMode();
277 }
278 }
279 }
280
281 purgeParent(what) {
282 if (!what) {
283 this.purge();
284 } else if (typeof what === "string") {
285 this.purge(dirname(what));
286 } else {
287 const set = new Set();
288 for (const item of what) {
289 set.add(dirname(item));
290 }
291 this.purge(set);
292 }
293 }
294
295 _storeResult(path, err, result) {
296 if (this._data.has(path)) return;
297 const level = this._levels[this._currentLevel];
298 this._data.set(path, { err, result, level });
299 level.add(path);
300 }
301
302 _decayLevel() {
303 const nextLevel = (this._currentLevel + 1) % this._levels.length;
304 const decay = this._levels[nextLevel];
305 this._currentLevel = nextLevel;
306 for (let item of decay) {
307 this._data.delete(item);
308 }
309 decay.clear();
310 if (this._data.size === 0) {
311 this._enterIdleMode();
312 } else {
313 // @ts-ignore _nextDecay is always a number in sync mode
314 this._nextDecay += this._tickInterval;
315 }
316 }
317
318 _runDecays() {
319 while (
320 /** @type {number} */ (this._nextDecay) <= Date.now() &&
321 this._mode !== STORAGE_MODE_IDLE
322 ) {
323 this._decayLevel();
324 }
325 }
326
327 _enterAsyncMode() {
328 let timeout = 0;
329 switch (this._mode) {
330 case STORAGE_MODE_ASYNC:
331 return;
332 case STORAGE_MODE_IDLE:
333 this._nextDecay = Date.now() + this._tickInterval;
334 timeout = this._tickInterval;
335 break;
336 case STORAGE_MODE_SYNC:
337 this._runDecays();
338 // @ts-ignore _runDecays may change the mode
339 if (this._mode === STORAGE_MODE_IDLE) return;
340 timeout = Math.max(
341 0,
342 /** @type {number} */ (this._nextDecay) - Date.now()
343 );
344 break;
345 }
346 this._mode = STORAGE_MODE_ASYNC;
347 const ref = setTimeout(() => {
348 this._mode = STORAGE_MODE_SYNC;
349 this._runDecays();
350 }, timeout);
351 if (ref.unref) ref.unref();
352 this._timeout = ref;
353 }
354
355 _enterSyncModeWhenIdle() {
356 if (this._mode === STORAGE_MODE_IDLE) {
357 this._mode = STORAGE_MODE_SYNC;
358 this._nextDecay = Date.now() + this._tickInterval;
359 }
360 }
361
362 _enterIdleMode() {
363 this._mode = STORAGE_MODE_IDLE;
364 this._nextDecay = undefined;
365 if (this._timeout) clearTimeout(this._timeout);
366 }
367}
368
369const createBackend = (duration, provider, syncProvider, providerContext) => {
370 if (duration > 0) {
371 return new CacheBackend(duration, provider, syncProvider, providerContext);
372 }
373 return new OperationMergerBackend(provider, syncProvider, providerContext);
374};
375
376module.exports = class CachedInputFileSystem {
377 constructor(fileSystem, duration) {
378 this.fileSystem = fileSystem;
379
380 this._lstatBackend = createBackend(
381 duration,
382 this.fileSystem.lstat,
383 this.fileSystem.lstatSync,
384 this.fileSystem
385 );
386 const lstat = this._lstatBackend.provide;
387 this.lstat = /** @type {FileSystem["lstat"]} */ (lstat);
388 const lstatSync = this._lstatBackend.provideSync;
389 this.lstatSync = /** @type {SyncFileSystem["lstatSync"]} */ (lstatSync);
390
391 this._statBackend = createBackend(
392 duration,
393 this.fileSystem.stat,
394 this.fileSystem.statSync,
395 this.fileSystem
396 );
397 const stat = this._statBackend.provide;
398 this.stat = /** @type {FileSystem["stat"]} */ (stat);
399 const statSync = this._statBackend.provideSync;
400 this.statSync = /** @type {SyncFileSystem["statSync"]} */ (statSync);
401
402 this._readdirBackend = createBackend(
403 duration,
404 this.fileSystem.readdir,
405 this.fileSystem.readdirSync,
406 this.fileSystem
407 );
408 const readdir = this._readdirBackend.provide;
409 this.readdir = /** @type {FileSystem["readdir"]} */ (readdir);
410 const readdirSync = this._readdirBackend.provideSync;
411 this.readdirSync = /** @type {SyncFileSystem["readdirSync"]} */ (readdirSync);
412
413 this._readFileBackend = createBackend(
414 duration,
415 this.fileSystem.readFile,
416 this.fileSystem.readFileSync,
417 this.fileSystem
418 );
419 const readFile = this._readFileBackend.provide;
420 this.readFile = /** @type {FileSystem["readFile"]} */ (readFile);
421 const readFileSync = this._readFileBackend.provideSync;
422 this.readFileSync = /** @type {SyncFileSystem["readFileSync"]} */ (readFileSync);
423
424 this._readJsonBackend = createBackend(
425 duration,
426 this.fileSystem.readJson ||
427 (this.readFile &&
428 ((path, callback) => {
429 // @ts-ignore
430 this.readFile(path, (err, buffer) => {
431 if (err) return callback(err);
432 if (!buffer || buffer.length === 0)
433 return callback(new Error("No file content"));
434 let data;
435 try {
436 data = JSON.parse(buffer.toString("utf-8"));
437 } catch (e) {
438 return callback(e);
439 }
440 callback(null, data);
441 });
442 })),
443 this.fileSystem.readJsonSync ||
444 (this.readFileSync &&
445 (path => {
446 const buffer = this.readFileSync(path);
447 const data = JSON.parse(buffer.toString("utf-8"));
448 return data;
449 })),
450 this.fileSystem
451 );
452 const readJson = this._readJsonBackend.provide;
453 this.readJson = /** @type {FileSystem["readJson"]} */ (readJson);
454 const readJsonSync = this._readJsonBackend.provideSync;
455 this.readJsonSync = /** @type {SyncFileSystem["readJsonSync"]} */ (readJsonSync);
456
457 this._readlinkBackend = createBackend(
458 duration,
459 this.fileSystem.readlink,
460 this.fileSystem.readlinkSync,
461 this.fileSystem
462 );
463 const readlink = this._readlinkBackend.provide;
464 this.readlink = /** @type {FileSystem["readlink"]} */ (readlink);
465 const readlinkSync = this._readlinkBackend.provideSync;
466 this.readlinkSync = /** @type {SyncFileSystem["readlinkSync"]} */ (readlinkSync);
467 }
468
469 purge(what) {
470 this._statBackend.purge(what);
471 this._lstatBackend.purge(what);
472 this._readdirBackend.purgeParent(what);
473 this._readFileBackend.purge(what);
474 this._readlinkBackend.purge(what);
475 this._readJsonBackend.purge(what);
476 }
477};
Note: See TracBrowser for help on using the repository browser.