Spaces:
Running
Running
| ; | |
| Object.defineProperty(exports, "__esModule", { | |
| value: true | |
| }); | |
| 0 && (module.exports = { | |
| Fallback: null, | |
| createCacheMap: null, | |
| deleteFromCacheMap: null, | |
| deleteMapEntry: null, | |
| getFromCacheMap: null, | |
| isValueExpired: null, | |
| setInCacheMap: null, | |
| setSizeInCacheMap: null | |
| }); | |
| function _export(target, all) { | |
| for(var name in all)Object.defineProperty(target, name, { | |
| enumerable: true, | |
| get: all[name] | |
| }); | |
| } | |
| _export(exports, { | |
| Fallback: function() { | |
| return Fallback; | |
| }, | |
| createCacheMap: function() { | |
| return createCacheMap; | |
| }, | |
| deleteFromCacheMap: function() { | |
| return deleteFromCacheMap; | |
| }, | |
| deleteMapEntry: function() { | |
| return deleteMapEntry; | |
| }, | |
| getFromCacheMap: function() { | |
| return getFromCacheMap; | |
| }, | |
| isValueExpired: function() { | |
| return isValueExpired; | |
| }, | |
| setInCacheMap: function() { | |
| return setInCacheMap; | |
| }, | |
| setSizeInCacheMap: function() { | |
| return setSizeInCacheMap; | |
| } | |
| }); | |
| const _lru = require("./lru"); | |
| const Fallback = {}; | |
| // This is a special internal key that is used for "revalidation" entries. It's | |
| // an implementation detail that shouldn't leak outside of this module. | |
| const Revalidation = {}; | |
| function createCacheMap() { | |
| const cacheMap = { | |
| parent: null, | |
| key: null, | |
| value: null, | |
| map: null, | |
| // LRU-related fields | |
| prev: null, | |
| next: null, | |
| size: 0 | |
| }; | |
| return cacheMap; | |
| } | |
| function getOrInitialize(cacheMap, keys, isRevalidation) { | |
| // Go through each level of keys until we find the entry that matches, or | |
| // create a new entry if one doesn't exist. | |
| // | |
| // This function will only return entries that match the keypath _exactly_. | |
| // Unlike getWithFallback, it will not access fallback entries unless it's | |
| // explicitly part of the keypath. | |
| let entry = cacheMap; | |
| let remainingKeys = keys; | |
| let key = null; | |
| while(true){ | |
| const previousKey = key; | |
| if (remainingKeys !== null) { | |
| key = remainingKeys.value; | |
| remainingKeys = remainingKeys.parent; | |
| } else if (isRevalidation && previousKey !== Revalidation) { | |
| // During a revalidation, we append an internal "Revalidation" key to | |
| // the end of the keypath. The "normal" entry is its parent. | |
| // However, if the parent entry is currently empty, we don't need to store | |
| // this as a revalidation entry. Just insert the revalidation into the | |
| // normal slot. | |
| if (entry.value === null) { | |
| return entry; | |
| } | |
| // Otheriwse, create a child entry. | |
| key = Revalidation; | |
| } else { | |
| break; | |
| } | |
| let map = entry.map; | |
| if (map !== null) { | |
| const existingEntry = map.get(key); | |
| if (existingEntry !== undefined) { | |
| // Found a match. Keep going. | |
| entry = existingEntry; | |
| continue; | |
| } | |
| } else { | |
| map = new Map(); | |
| entry.map = map; | |
| } | |
| // No entry exists yet at this level. Create a new one. | |
| const newEntry = { | |
| parent: entry, | |
| key, | |
| value: null, | |
| map: null, | |
| // LRU-related fields | |
| prev: null, | |
| next: null, | |
| size: 0 | |
| }; | |
| map.set(key, newEntry); | |
| entry = newEntry; | |
| } | |
| return entry; | |
| } | |
| function getFromCacheMap(now, currentCacheVersion, rootEntry, keys, isRevalidation) { | |
| const entry = getEntryWithFallbackImpl(now, currentCacheVersion, rootEntry, keys, isRevalidation, 0); | |
| if (entry === null || entry.value === null) { | |
| return null; | |
| } | |
| // This is an LRU access. Move the entry to the front of the list. | |
| (0, _lru.lruPut)(entry); | |
| return entry.value; | |
| } | |
| function isValueExpired(now, currentCacheVersion, value) { | |
| return value.staleAt <= now || value.version < currentCacheVersion; | |
| } | |
| function lazilyEvictIfNeeded(now, currentCacheVersion, entry) { | |
| // We have a matching entry, but before we can return it, we need to check if | |
| // it's still fresh. Otherwise it should be treated the same as a cache miss. | |
| if (entry.value === null) { | |
| // This entry has no value, so there's nothing to evict. | |
| return entry; | |
| } | |
| const value = entry.value; | |
| if (isValueExpired(now, currentCacheVersion, value)) { | |
| // The value expired. Lazily evict it from the cache, and return null. This | |
| // is conceptually the same as a cache miss. | |
| deleteMapEntry(entry); | |
| return null; | |
| } | |
| // The matched entry has not expired. Return it. | |
| return entry; | |
| } | |
| function getEntryWithFallbackImpl(now, currentCacheVersion, entry, keys, isRevalidation, previousKey) { | |
| // This is similar to getExactEntry, but if an exact match is not found for | |
| // a key, it will return the fallback entry instead. This is recursive at | |
| // every level, e.g. an entry with keypath [a, Fallback, c, Fallback] is | |
| // valid match for [a, b, c, d]. | |
| // | |
| // It will return the most specific match available. | |
| let key; | |
| let remainingKeys; | |
| if (keys !== null) { | |
| key = keys.value; | |
| remainingKeys = keys.parent; | |
| } else if (isRevalidation && previousKey !== Revalidation) { | |
| // During a revalidation, we append an internal "Revalidation" key to | |
| // the end of the keypath. | |
| key = Revalidation; | |
| remainingKeys = null; | |
| } else { | |
| // There are no more keys. This is the terminal entry. | |
| // TODO: When performing a lookup during a navigation, as opposed to a | |
| // prefetch, we may want to skip entries that are Pending if there's also | |
| // a Fulfilled fallback entry. Tricky to say, though, since if it's | |
| // already pending, it's likely to stream in soon. Maybe we could do this | |
| // just on slow connections and offline mode. | |
| return lazilyEvictIfNeeded(now, currentCacheVersion, entry); | |
| } | |
| const map = entry.map; | |
| if (map !== null) { | |
| const existingEntry = map.get(key); | |
| if (existingEntry !== undefined) { | |
| // Found an exact match for this key. Keep searching. | |
| const result = getEntryWithFallbackImpl(now, currentCacheVersion, existingEntry, remainingKeys, isRevalidation, key); | |
| if (result !== null) { | |
| return result; | |
| } | |
| } | |
| // No match found for this key. Check if there's a fallback. | |
| const fallbackEntry = map.get(Fallback); | |
| if (fallbackEntry !== undefined) { | |
| // Found a fallback for this key. Keep searching. | |
| return getEntryWithFallbackImpl(now, currentCacheVersion, fallbackEntry, remainingKeys, isRevalidation, key); | |
| } | |
| } | |
| return null; | |
| } | |
| function setInCacheMap(cacheMap, keys, value, isRevalidation) { | |
| // Add a value to the map at the given keypath. If the value is already | |
| // part of the map, it's removed from its previous keypath. (NOTE: This is | |
| // unlike a regular JS map, but the behavior is intentional.) | |
| const entry = getOrInitialize(cacheMap, keys, isRevalidation); | |
| setMapEntryValue(entry, value); | |
| // This is an LRU access. Move the entry to the front of the list. | |
| (0, _lru.lruPut)(entry); | |
| (0, _lru.updateLruSize)(entry, value.size); | |
| } | |
| function setMapEntryValue(entry, value) { | |
| if (entry.value !== null) { | |
| // There's already a value at the given keypath. Disconnect the old value | |
| // from the map. We're not calling `deleteMapEntry` here because the | |
| // entry itself is still in the map. We just want to overwrite its value. | |
| dropRef(entry.value); | |
| entry.value = null; | |
| } | |
| // This value may already be in the map at a different keypath. | |
| // Grab a reference before we overwrite it. | |
| const oldEntry = value.ref; | |
| entry.value = value; | |
| value.ref = entry; | |
| (0, _lru.updateLruSize)(entry, value.size); | |
| if (oldEntry !== null && oldEntry !== entry && oldEntry.value === value) { | |
| // This value is already in the map at a different keypath in the map. | |
| // Values only exist at a single keypath at a time. Remove it from the | |
| // previous keypath. | |
| // | |
| // Note that only the internal map entry is garbage collected; we don't | |
| // call `dropRef` here because it's still in the map, just | |
| // at a new keypath (the one we just set, above). | |
| deleteMapEntry(oldEntry); | |
| } | |
| } | |
| function deleteFromCacheMap(value) { | |
| const entry = value.ref; | |
| if (entry === null) { | |
| // This value is not a member of any map. | |
| return; | |
| } | |
| dropRef(value); | |
| deleteMapEntry(entry); | |
| } | |
| function dropRef(value) { | |
| // Drop the value from the map by setting its `ref` backpointer to | |
| // null. This is a separate operation from `deleteMapEntry` because when | |
| // re-keying a value we need to be able to delete the old, internal map | |
| // entry without garbage collecting the value itself. | |
| value.ref = null; | |
| } | |
| function deleteMapEntry(entry) { | |
| // Delete the entry from the cache. | |
| entry.value = null; | |
| (0, _lru.deleteFromLru)(entry); | |
| // Check if we can garbage collect the entry. | |
| const map = entry.map; | |
| if (map === null) { | |
| // Since this entry has no value, and also no child entries, we can | |
| // garbage collect it. Remove it from its parent, and keep garbage | |
| // collecting the parents until we reach a non-empty entry. | |
| let parent = entry.parent; | |
| let key = entry.key; | |
| while(parent !== null){ | |
| const parentMap = parent.map; | |
| if (parentMap !== null) { | |
| parentMap.delete(key); | |
| if (parentMap.size === 0) { | |
| // We just removed the last entry in the parent map. | |
| parent.map = null; | |
| if (parent.value === null) { | |
| // The parent node has no child entries, nor does it have a value | |
| // on itself. It can be garbage collected. Keep going. | |
| key = parent.key; | |
| parent = parent.parent; | |
| continue; | |
| } | |
| } | |
| } | |
| break; | |
| } | |
| } else { | |
| // Check if there's a revalidating entry. If so, promote it to a | |
| // "normal" entry, since the normal one was just deleted. | |
| const revalidatingEntry = map.get(Revalidation); | |
| if (revalidatingEntry !== undefined && revalidatingEntry.value !== null) { | |
| setMapEntryValue(entry, revalidatingEntry.value); | |
| } | |
| } | |
| } | |
| function setSizeInCacheMap(value, size) { | |
| const entry = value.ref; | |
| if (entry === null) { | |
| // This value is not a member of any map. | |
| return; | |
| } | |
| // Except during initialization (when the size is set to 0), this is the only | |
| // place the `size` field should be updated, to ensure it's in sync with the | |
| // the LRU. | |
| value.size = size; | |
| (0, _lru.updateLruSize)(entry, size); | |
| } | |
| if ((typeof exports.default === 'function' || (typeof exports.default === 'object' && exports.default !== null)) && typeof exports.default.__esModule === 'undefined') { | |
| Object.defineProperty(exports.default, '__esModule', { value: true }); | |
| Object.assign(exports.default, exports); | |
| module.exports = exports.default; | |
| } | |
| //# sourceMappingURL=cache-map.js.map |