react-native-onyx
Version:
State management for React Native
131 lines (130 loc) • 5.82 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.OnyxSnapshotCache = void 0;
const OnyxUtils_1 = __importDefault(require("./OnyxUtils"));
/**
* Manages snapshot caching for useOnyx hook performance optimization.
* Handles selector function tracking and memoized getSnapshot results.
*/
class OnyxSnapshotCache {
constructor() {
this.snapshotCache = new Map();
this.selectorIDMap = new Map();
this.selectorIDCounter = 0;
this.cacheKeyRefCounts = new Map();
}
/**
* Generate unique ID for selector functions using incrementing numbers
*/
getSelectorID(selector) {
const typedSelector = selector;
if (!this.selectorIDMap.has(typedSelector)) {
const id = this.selectorIDCounter++;
this.selectorIDMap.set(typedSelector, id);
}
return this.selectorIDMap.get(typedSelector);
}
/**
* Register a consumer for a cache key and return the cache key.
* Generates cache key and increments reference counter.
*
* The properties used to generate the cache key are handpicked for performance reasons and
* according to their purpose and effect they produce in the useOnyx hook behavior:
*
* - `selector`: Different selectors produce different results, so each selector needs its own cache entry
* - `initWithStoredValues`: This flag changes the initial loading behavior and affects the returned fetch status
* - `allowStaleData`: Controls whether stale data can be returned during pending merges, affecting result timing
* - `canBeMissing`: Determines logging behavior for missing data, but doesn't affect the actual data returned
*
* Other options like `canEvict`, `reuseConnection`, and `allowDynamicKey` don't affect the data transformation
* or timing behavior of getSnapshot, so they're excluded from the cache key for better cache hit rates.
*/
registerConsumer(options) {
var _a, _b, _c;
const selectorID = (options === null || options === void 0 ? void 0 : options.selector) ? this.getSelectorID(options.selector) : 'no_selector';
// Create options hash without expensive JSON.stringify
const initWithStoredValues = (_a = options === null || options === void 0 ? void 0 : options.initWithStoredValues) !== null && _a !== void 0 ? _a : true;
const allowStaleData = (_b = options === null || options === void 0 ? void 0 : options.allowStaleData) !== null && _b !== void 0 ? _b : false;
const canBeMissing = (_c = options === null || options === void 0 ? void 0 : options.canBeMissing) !== null && _c !== void 0 ? _c : true;
const cacheKey = `${selectorID}_${initWithStoredValues}_${allowStaleData}_${canBeMissing}`;
// Increment reference count for this cache key
const currentCount = this.cacheKeyRefCounts.get(cacheKey) || 0;
this.cacheKeyRefCounts.set(cacheKey, currentCount + 1);
return cacheKey;
}
/**
* Deregister a consumer for a cache key.
* Decrements reference counter and removes cache entry if no consumers remain.
*/
deregisterConsumer(key, cacheKey) {
const currentCount = this.cacheKeyRefCounts.get(cacheKey) || 0;
if (currentCount <= 1) {
// Last consumer - remove from reference counter and cache
this.cacheKeyRefCounts.delete(cacheKey);
// Remove from snapshot cache
const keyCache = this.snapshotCache.get(key);
if (keyCache) {
keyCache.delete(cacheKey);
// If this was the last cache entry for this Onyx key, remove the key entirely
if (keyCache.size === 0) {
this.snapshotCache.delete(key);
}
}
}
else {
// Still has other consumers - just decrement count
this.cacheKeyRefCounts.set(cacheKey, currentCount - 1);
}
}
/**
* Get cached snapshot result for a key and cache key combination
*/
getCachedResult(key, cacheKey) {
const keyCache = this.snapshotCache.get(key);
return keyCache === null || keyCache === void 0 ? void 0 : keyCache.get(cacheKey);
}
/**
* Set cached snapshot result for a key and cache key combination
*/
setCachedResult(key, cacheKey, result) {
if (!this.snapshotCache.has(key)) {
this.snapshotCache.set(key, new Map());
}
this.snapshotCache.get(key).set(cacheKey, result);
}
/**
* Selective cache invalidation to prevent data unavailability
* Collection members invalidate upward, collections don't cascade downward
*/
invalidateForKey(keyToInvalidate) {
// Always invalidate the exact key
this.snapshotCache.delete(keyToInvalidate);
try {
// Check if the key is a collection member and invalidate the collection base key
const collectionBaseKey = OnyxUtils_1.default.getCollectionKey(keyToInvalidate);
this.snapshotCache.delete(collectionBaseKey);
}
catch (e) {
// do nothing - this just means the key is not a collection member
}
}
/**
* Clear all snapshot cache
*/
clear() {
this.snapshotCache.clear();
}
/**
* Clear selector ID mappings (useful for testing)
*/
clearSelectorIds() {
this.selectorIDCounter = 0;
}
}
exports.OnyxSnapshotCache = OnyxSnapshotCache;
// Create and export a singleton instance
const onyxSnapshotCache = new OnyxSnapshotCache();
exports.default = onyxSnapshotCache;