cacheWeakMapFactory
Creates a memory-efficient WeakMap-based cache optimized for object-keyed storage. Provides a wrapper around native WeakMap with enhanced API for caching scenarios where object references serve as keys. Offers automatic garbage collection benefits as entries are automatically removed when key objects are no longer referenced elsewhere, preventing memory leaks in long-running applications.
Signature
const cacheWeakMapFactory: <V, K extends object = object>(defaultValue?: WeakMap<K, V>) => {
/**
* Returns the original WeakMap object
* @returns The original WeakMap object
*/
getCache: () => WeakMap<K, V>;
/**
* Checks if the given key exists in the cache
* @param key - The key to check
* @returns Whether the key exists
*/
has: (key: K) => boolean;
/**
* Gets the value for the given key
* @param key - The key to find the value for
* @returns The value corresponding to the key or undefined
*/
get: (key: K) => V | undefined;
/**
* Stores a value for the given key
* @param key - The key to store
* @param value - The value to store
* @returns The cache object itself for method chaining
*/
set: (key: K, value: V) => WeakMap<K, V>;
/**
* Deletes the key and its corresponding value from the cache
* @param key - The key to delete
* @returns Whether the deletion was successful
*/
delete: (key: K) => boolean;
}
Parameters
| Name | Type | Description |
|---|---|---|
defaultValue | - | Optional existing WeakMap instance for initialization |
Returns
Enhanced WeakMap cache with convenient access methods
Examples
Component instance caching
import { cacheWeakMapFactory } from '@winglet/common-utils';
interface ComponentConfig {
theme: string;
size: 'small' | 'medium' | 'large';
disabled: boolean;
}
const componentCache = cacheWeakMapFactory<ComponentConfig, HTMLElement>();
function setupComponent(element: HTMLElement) {
if (componentCache.has(element)) {
return componentCache.get(element)!;
}
const config: ComponentConfig = {
theme: element.dataset.theme || 'default',
size: (element.dataset.size as any) || 'medium',
disabled: element.hasAttribute('disabled')
};
componentCache.set(element, config);
return config;
}
// When element is removed from DOM and no longer referenced,
// the cache entry is automatically garbage collected
Function memoization with object parameters
interface QueryParams {
filters: Record<string, any>;
sort: string;
page: number;
}
const queryCache = cacheWeakMapFactory<Promise<ApiResponse>, QueryParams>();
async function fetchData(params: QueryParams): Promise<ApiResponse> {
if (queryCache.has(params)) {
console.log('Returning cached result');
return queryCache.get(params)!;
}
console.log('Fetching new data');
const promise = fetch('/api/data', {
method: 'POST',
body: JSON.stringify(params)
}).then(r => r.json());
queryCache.set(params, promise);
return promise;
}
// Usage
const params1 = { filters: { status: 'active' }, sort: 'name', page: 1 };
const params2 = { filters: { status: 'active' }, sort: 'name', page: 1 };
await fetchData(params1); // Fetches from API
await fetchData(params1); // Returns cached result
await fetchData(params2); // Fetches from API (different object reference)
User session management
interface UserSession {
userId: string;
permissions: string[];
lastActivity: Date;
preferences: Record<string, any>;
}
const sessionCache = cacheWeakMapFactory<UserSession, Request>();
function getOrCreateSession(request: Request): UserSession {
if (sessionCache.has(request)) {
const session = sessionCache.get(request)!;
session.lastActivity = new Date();
return session;
}
const session: UserSession = {
userId: extractUserId(request),
permissions: extractPermissions(request),
lastActivity: new Date(),
preferences: {}
};
sessionCache.set(request, session);
return session;
}
// Session data is automatically cleaned up when request objects
// are garbage collected after response completion
DOM event handler management
interface EventHandlers {
click?: EventListener;
hover?: EventListener;
focus?: EventListener;
}
const handlerCache = cacheWeakMapFactory<EventHandlers, Element>();
function attachEventHandlers(element: Element, handlers: EventHandlers) {
// Check if handlers already attached
if (handlerCache.has(element)) {
console.log('Handlers already attached to element');
return;
}
// Attach new handlers
if (handlers.click) element.addEventListener('click', handlers.click);
if (handlers.hover) element.addEventListener('mouseenter', handlers.hover);
if (handlers.focus) element.addEventListener('focus', handlers.focus);
// Cache the handlers for future reference
handlerCache.set(element, handlers);
}
function getAttachedHandlers(element: Element): EventHandlers | undefined {
return handlerCache.get(element);
}
Computed property caching
interface ComputedValues {
expensiveCalculation: number;
formattedData: string[];
validationResults: ValidationResult[];
}
const computedCache = cacheWeakMapFactory<ComputedValues, DataModel>();
function getComputedValues(model: DataModel): ComputedValues {
if (computedCache.has(model)) {
return computedCache.get(model)!;
}
const computed: ComputedValues = {
expensiveCalculation: performExpensiveCalculation(model),
formattedData: formatDataForDisplay(model),
validationResults: validateModel(model)
};
computedCache.set(model, computed);
return computed;
}
Playground
import { cacheWeakMapFactory } from '@winglet/common-utils'; interface ComponentConfig { theme: string; size: 'small' | 'medium' | 'large'; disabled: boolean; } const componentCache = cacheWeakMapFactory<ComponentConfig, HTMLElement>(); function setupComponent(element: HTMLElement) { if (componentCache.has(element)) { return componentCache.get(element)!; } const config: ComponentConfig = { theme: element.dataset.theme || 'default', size: (element.dataset.size as any) || 'medium', disabled: element.hasAttribute('disabled') }; componentCache.set(element, config); return config; } // When element is removed from DOM and no longer referenced, // the cache entry is automatically garbage collected
Notes
Key Advantages:
- Automatic Memory Management: Entries are garbage collected when keys are no longer referenced
- Memory Leak Prevention: No risk of memory leaks from forgotten cache entries
- Object Identity Based: Uses object reference equality for key comparison
- High Performance: O(1) average time complexity for all operations
- Type Safety: Full TypeScript support with generic constraints
Limitations:
- Object Keys Only: Keys must be objects, not primitives
- No Enumeration: Cannot iterate over WeakMap entries (by design)
- No Size Property: Cannot determine number of entries
- No Clear Method: Cannot bulk clear all entries
Best Use Cases:
- DOM Element Associations: Link data to DOM elements
- Object Metadata: Store additional data for existing objects
- Request/Response Caching: Cache data tied to request objects
- Component State: Associate state with component instances
- Temporary Associations: Create temporary object relationships
Memory Benefits: WeakMap entries don't prevent key objects from being garbage collected, making it ideal for scenarios where you want to associate data with objects without extending their lifetime.