@equinor/fusion-query
Version:
Reactive data fetching and caching library with observable streams and comprehensive event system
186 lines • 7.08 kB
JavaScript
import { Subject } from 'rxjs';
import { FlowSubject } from '@equinor/fusion-observable';
import { actions } from './actions';
import createReducer from './create-reducer';
import { QueryCacheEvent } from './events';
/**
* A class that represents a cache for storing query results.
* @template TType - The type of the response data.
* @template TArgs - The type of the arguments used to fetch the data.
*/
export class QueryCache {
/**
* The internal state of the query cache, represented as a FlowSubject.
*/
#state;
/**
* Events subject for emitting QueryCache lifecycle events.
*
* This subject broadcasts events that track cache operations,
* allowing external observers to monitor cache changes and mutations.
*/
#events;
/**
* Retrieves the current state of the query cache as a record.
* @returns {Record<string, QueryCacheRecord<TType, TArgs>>} The current state.
*/
get state() {
return this.#state.value;
}
/**
* Retrieves an Observable that emits the current state of the query cache.
* @returns {Observable<Record<string, QueryCacheRecord<TType, TArgs>>>} An observable of the current state.
*/
get state$() {
return this.#state.asObservable();
}
/**
* Retrieves an Observable that emits the actions dispatched to the query cache.
* @returns {Observable<Actions<TType, TArgs>>} An observable of the dispatched actions.
*/
get action$() {
return this.#state.action$;
}
/**
* Protected method to emit QueryCache lifecycle events.
*
* This method creates and emits events that track the various operations performed
* on the cache, allowing external observers to monitor cache changes and mutations.
*
* @template TType - The specific event type from QueryCacheEvents
* @param type - The event type identifier
* @param key - The cache key associated with this event
* @param data - Optional event-specific data payload (type-safe based on event type)
* @protected
*/
_registerEvent(type, args) {
this.#events.next(new QueryCacheEvent(type, args?.key, args?.data));
}
/**
* An Observable stream of QueryCache lifecycle events.
*
* This stream emits events that track the various operations performed on the cache,
* including entry set, inserted, removed, invalidated, mutated, and trimmed events.
* Subscribers can monitor cache changes and handle different lifecycle events.
*
* @returns An Observable stream of QueryCache events
*/
get event$() {
return this.#events.asObservable();
}
/**
* Creates a new instance of QueryCache.
* @param {QueryCacheCtorArgs<TType, TArgs>} args - The constructor arguments.
*/
constructor(args) {
const { trimming, initial } = args ?? {};
this.#state = new FlowSubject(createReducer(actions, initial));
if (trimming) {
this.#state.addEffect('cache/set', () => this.#state.next(actions.trim(trimming)));
}
// Initialize events subject for broadcasting lifecycle events
this.#events = new Subject();
}
/**
* Checks if an item exists in the query cache by key.
* @param {string} key - The key of the item to check.
* @returns {boolean} True if the item exists, otherwise false.
*/
has(key) {
return key in this.#state.value;
}
/**
* Sets the value of an item in the query cache.
* @param {string} key - The key of the item to set.
* @param {Pick<QueryCacheRecord<TType, TArgs>, 'value' | 'args' | 'transaction'>} record - The new value of the item.
*/
setItem(key, record) {
const { args, transaction, value } = record;
this.#state.next(actions.insert(key, { args, transaction, value }));
this._registerEvent('query_cache_entry_inserted', { key, data: { value, args, transaction } });
}
/**
* Retrieves an item from the query cache by key.
* @param {string} key - The key of the item to retrieve.
* @returns {QueryCacheRecord<TType, TArgs> | undefined} The cached item or undefined if not found.
*/
getItem(key) {
return this.#state.value[key];
}
/**
* Removes an item from the query cache by key.
* @param {string} key - The key of the item to remove.
*/
removeItem(key) {
this.#state.next(actions.remove(key));
this._registerEvent('query_cache_entry_removed', { key });
}
/**
* Invalidates an item in the query cache by key, causing it to be refetched on next request.
* @param {string} key - The key of the item to invalidate.
*/
invalidate(key) {
const item = key ? this.#state.value[key] : undefined;
this.#state.next(actions.invalidate(key, item));
if (key) {
this._registerEvent('query_cache_entry_invalidated', {
key,
data: { previousValue: item?.value },
});
}
}
/**
* Mutates an item in the query cache by key.
* @param {string} key - The key of the item to mutate.
* @param {QueryCacheMutation | ((current: TType) => QueryCacheMutation)} changes - The changes to apply to the item.
*/
mutate(key, changes) {
const current = key in this.#state.value ? this.#state.value[key] : undefined;
if (!current) {
throw new Error(`Cannot mutate cache item with key ${key}: item not found`);
}
const next = typeof changes === 'function' ? changes(current?.value) : changes;
this.#state.next(actions.mutate(key, next, current));
this._registerEvent('query_cache_entry_mutated', {
key,
data: {
previousValue: current.value,
newValue: next,
mutation: next,
},
});
return () => this.#state.next(actions.set(key, current));
}
/**
* Trims the query cache based on the provided options.
* @param {TrimOptions<TType, TArgs>} options - The options for trimming the cache.
*/
trim(options) {
const beforeKeys = new Set(Object.keys(this.#state.value));
this.#state.next(actions.trim(options));
const afterKeys = new Set(Object.keys(this.#state.value));
const removedKeys = Array.from(beforeKeys).filter((key) => !afterKeys.has(key));
this._registerEvent('query_cache_trimmed', {
data: {
removedKeys,
criteria: options,
},
});
}
/**
* Resets the query cache to its initial state.
*/
reset() {
this.#state.reset();
this._registerEvent('query_cache_reset');
}
/**
* Completes the query cache, signaling that no more items will be added to the cache.
*/
complete() {
this.#state.complete();
this.#events.complete();
}
}
export default QueryCache;
//# sourceMappingURL=QueryCache.js.map