@jay-js/system
Version:
A powerful and flexible TypeScript library for UI, state management, lazy loading, routing and managing draggable elements in modern web applications.
242 lines • 6.68 kB
JavaScript
/**
* Global query cache singleton
* Manages cached data across all queries with automatic garbage collection
*/
class QueryCache {
constructor() {
this.cache = new Map();
this.gcTimers = new Map();
this.listeners = new Map();
}
/**
* Get cached data for a key
*
* @param key Query key
* @returns Cache entry or undefined if not found
*/
get(key) {
return this.cache.get(key);
}
/**
* Set cached data for a key
*
* @param key Query key
* @param data Data to cache
* @param cacheTime Time in ms before garbage collection
*/
set(key, data, cacheTime) {
const existing = this.cache.get(key);
this.cache.set(key, {
data,
timestamp: Date.now(),
subscribers: existing ? existing.subscribers : 1,
});
this.scheduleGC(key, cacheTime);
const listeners = this.listeners.get(key);
if (listeners) {
for (const callback of listeners) {
callback(data);
}
}
}
/**
* Check if data is stale
*
* @param key Query key
* @param staleTime Time in ms before data is considered stale
* @returns True if data is stale or not found
*/
isStale(key, staleTime) {
const entry = this.cache.get(key);
if (!entry)
return true;
const age = Date.now() - entry.timestamp;
return age > staleTime;
}
/**
* Delete cache entry
*
* @param key Query key
*/
delete(key) {
this.cache.delete(key);
const timer = this.gcTimers.get(key);
if (timer) {
clearTimeout(timer);
this.gcTimers.delete(key);
}
}
/**
* Increment subscriber count
*
* @param key Query key
*/
subscribe(key) {
const entry = this.cache.get(key);
if (entry) {
entry.subscribers++;
}
}
/**
* Decrement subscriber count
*
* @param key Query key
*/
unsubscribe(key) {
const entry = this.cache.get(key);
if (entry) {
entry.subscribers = Math.max(0, entry.subscribers - 1);
}
}
/**
* Register a listener for cache changes on a specific key
*
* @param key Query key to watch
* @param callback Function to call when cache is updated
* @returns Cleanup function to remove the listener
*/
onChange(key, callback) {
var _a;
if (!this.listeners.has(key)) {
this.listeners.set(key, new Set());
}
(_a = this.listeners.get(key)) === null || _a === void 0 ? void 0 : _a.add(callback);
return () => this.offChange(key, callback);
}
/**
* Remove a listener for a specific key
*
* @param key Query key
* @param callback Callback to remove
*/
offChange(key, callback) {
var _a, _b;
(_a = this.listeners.get(key)) === null || _a === void 0 ? void 0 : _a.delete(callback);
if (((_b = this.listeners.get(key)) === null || _b === void 0 ? void 0 : _b.size) === 0) {
this.listeners.delete(key);
}
}
/**
* Schedule garbage collection for inactive query
*
* @param key Query key
* @param cacheTime Time in ms before garbage collection
*/
scheduleGC(key, cacheTime) {
const existing = this.gcTimers.get(key);
if (existing) {
clearTimeout(existing);
}
const timer = setTimeout(() => {
const entry = this.cache.get(key);
if (entry && entry.subscribers === 0) {
this.delete(key);
}
}, cacheTime);
this.gcTimers.set(key, timer);
}
/**
* Clear all cache
*/
clear() {
this.cache.clear();
for (const timer of this.gcTimers.values()) {
clearTimeout(timer);
}
this.gcTimers.clear();
this.listeners.clear();
}
/**
* Invalidate queries matching a pattern
* Supports glob patterns (* and ?) and RegExp
*
* @param pattern Glob pattern string or RegExp to match keys
* @returns Array of invalidated keys
*
* @example
* ```typescript
* // Invalidate all user queries
* queryCache.invalidatePattern('user-*');
*
* // Invalidate with regex
* queryCache.invalidatePattern(/^user-\d+$/);
* ```
*/
invalidatePattern(pattern) {
const keysToInvalidate = [];
for (const [key] of this.cache) {
if (this.matchesPattern(key, pattern)) {
keysToInvalidate.push(key);
}
}
for (const key of keysToInvalidate) {
this.delete(key);
}
return keysToInvalidate;
}
/**
* Invalidate queries matching a predicate function
*
* @param predicate Function that returns true for keys to invalidate
* @returns Array of invalidated keys
*
* @example
* ```typescript
* // Invalidate all stale queries
* queryCache.invalidateQueries((key, entry) => {
* const age = Date.now() - entry.timestamp;
* return age > 60000; // 1 minute
* });
* ```
*/
invalidateQueries(predicate) {
const keysToInvalidate = [];
for (const [key, entry] of this.cache) {
if (predicate(key, entry)) {
keysToInvalidate.push(key);
}
}
for (const key of keysToInvalidate) {
this.delete(key);
}
return keysToInvalidate;
}
/**
* Get all cache keys
*
* @returns Array of all cached query keys
*/
getKeys() {
return Array.from(this.cache.keys());
}
/**
* Match key against pattern
*
* @param key Query key
* @param pattern String glob pattern or RegExp
* @returns True if key matches pattern
*/
matchesPattern(key, pattern) {
if (pattern instanceof RegExp) {
return pattern.test(key);
}
const regexPattern = pattern
.replace(/\*/g, "__STAR__")
.replace(/\?/g, "__QUESTION__")
.replace(/[.+^${}()|[\]\\]/g, "\\$&")
.replace(/__STAR__/g, ".*")
.replace(/__QUESTION__/g, ".");
return new RegExp(`^${regexPattern}$`).test(key);
}
/**
* Get cache size
*/
get size() {
return this.cache.size;
}
}
/**
* Global cache singleton instance
*/
export const queryCache = new QueryCache();
//# sourceMappingURL=cache.js.map