mirror of
https://github.com/eigent-ai/eigent.git
synced 2026-04-29 04:00:09 +00:00
167 lines
5 KiB
JavaScript
167 lines
5 KiB
JavaScript
// src/utils/caches.tsx
|
|
import { DependenciesMap } from "./maps";
|
|
import { filterUndefined } from "./objects";
|
|
import { pending, rateLimited, resolved, runAsynchronously, wait } from "./promises";
|
|
import { AsyncStore } from "./stores";
|
|
function cacheFunction(f) {
|
|
const dependenciesMap = new DependenciesMap();
|
|
return (...args) => {
|
|
if (dependenciesMap.has(args)) {
|
|
return dependenciesMap.get(args);
|
|
}
|
|
const value = f(...args);
|
|
dependenciesMap.set(args, value);
|
|
return value;
|
|
};
|
|
}
|
|
var AsyncCache = class {
|
|
constructor(_fetcher, _options = {}) {
|
|
this._fetcher = _fetcher;
|
|
this._options = _options;
|
|
this._map = new DependenciesMap();
|
|
this.isCacheAvailable = this._createKeyed("isCacheAvailable");
|
|
this.getIfCached = this._createKeyed("getIfCached");
|
|
this.getOrWait = this._createKeyed("getOrWait");
|
|
this.forceSetCachedValue = this._createKeyed("forceSetCachedValue");
|
|
this.forceSetCachedValueAsync = this._createKeyed("forceSetCachedValueAsync");
|
|
this.refresh = this._createKeyed("refresh");
|
|
this.invalidate = this._createKeyed("invalidate");
|
|
this.onStateChange = this._createKeyed("onStateChange");
|
|
}
|
|
_createKeyed(functionName) {
|
|
return (key, ...args) => {
|
|
const valueCache = this.getValueCache(key);
|
|
return valueCache[functionName].apply(valueCache, args);
|
|
};
|
|
}
|
|
getValueCache(dependencies) {
|
|
let cache = this._map.get(dependencies);
|
|
if (!cache) {
|
|
cache = new AsyncValueCache(
|
|
async () => await this._fetcher(dependencies),
|
|
{
|
|
...this._options,
|
|
onSubscribe: this._options.onSubscribe ? (cb) => this._options.onSubscribe(dependencies, cb) : void 0
|
|
}
|
|
);
|
|
this._map.set(dependencies, cache);
|
|
}
|
|
return cache;
|
|
}
|
|
async refreshWhere(predicate) {
|
|
const promises = [];
|
|
for (const [dependencies, cache] of this._map) {
|
|
if (predicate(dependencies)) {
|
|
promises.push(cache.refresh());
|
|
}
|
|
}
|
|
await Promise.all(promises);
|
|
}
|
|
};
|
|
var AsyncValueCache = class {
|
|
constructor(fetcher, _options = {}) {
|
|
this._options = _options;
|
|
this._subscriptionsCount = 0;
|
|
this._unsubscribers = [];
|
|
this._mostRecentRefreshPromiseIndex = 0;
|
|
this._store = new AsyncStore();
|
|
this._rateLimitOptions = {
|
|
concurrency: 1,
|
|
throttleMs: 300,
|
|
...filterUndefined(_options.rateLimiter ?? {})
|
|
};
|
|
this._fetcher = rateLimited(fetcher, {
|
|
...this._rateLimitOptions,
|
|
batchCalls: true
|
|
});
|
|
}
|
|
isCacheAvailable() {
|
|
return this._store.isAvailable();
|
|
}
|
|
getIfCached() {
|
|
return this._store.get();
|
|
}
|
|
getOrWait(cacheStrategy) {
|
|
const cached = this.getIfCached();
|
|
if (cacheStrategy === "read-write" && cached.status === "ok") {
|
|
return resolved(cached.data);
|
|
}
|
|
return this._refetch(cacheStrategy);
|
|
}
|
|
_set(value) {
|
|
this._store.set(value);
|
|
}
|
|
_setAsync(value) {
|
|
const promise = pending(value);
|
|
this._pendingPromise = promise;
|
|
return pending(this._store.setAsync(promise));
|
|
}
|
|
_refetch(cacheStrategy) {
|
|
if (cacheStrategy === "read-write" && this._pendingPromise) {
|
|
return this._pendingPromise;
|
|
}
|
|
const promise = pending(this._fetcher());
|
|
if (cacheStrategy === "never") {
|
|
return promise;
|
|
}
|
|
return pending(this._setAsync(promise).then(() => promise));
|
|
}
|
|
forceSetCachedValue(value) {
|
|
this._set(value);
|
|
}
|
|
forceSetCachedValueAsync(value) {
|
|
return this._setAsync(value);
|
|
}
|
|
/**
|
|
* Refetches the value from the fetcher, and updates the cache with it.
|
|
*/
|
|
async refresh() {
|
|
return await this.getOrWait("write-only");
|
|
}
|
|
/**
|
|
* Invalidates the cache, marking it to refresh on the next read. If anyone was listening to it, it will refresh
|
|
* immediately.
|
|
*/
|
|
invalidate() {
|
|
this._store.setUnavailable();
|
|
this._pendingPromise = void 0;
|
|
if (this._subscriptionsCount > 0) {
|
|
runAsynchronously(this.refresh());
|
|
}
|
|
}
|
|
onStateChange(callback) {
|
|
const storeObj = this._store.onChange(callback);
|
|
runAsynchronously(this.getOrWait("read-write"));
|
|
if (this._subscriptionsCount++ === 0 && this._options.onSubscribe) {
|
|
const unsubscribe = this._options.onSubscribe(() => {
|
|
runAsynchronously(this.refresh());
|
|
});
|
|
this._unsubscribers.push(unsubscribe);
|
|
}
|
|
let hasUnsubscribed = false;
|
|
return {
|
|
unsubscribe: () => {
|
|
if (hasUnsubscribed) return;
|
|
hasUnsubscribed = true;
|
|
storeObj.unsubscribe();
|
|
if (--this._subscriptionsCount === 0) {
|
|
const currentRefreshPromiseIndex = ++this._mostRecentRefreshPromiseIndex;
|
|
runAsynchronously(async () => {
|
|
await wait(5e3);
|
|
if (this._subscriptionsCount === 0 && currentRefreshPromiseIndex === this._mostRecentRefreshPromiseIndex) {
|
|
this.invalidate();
|
|
}
|
|
});
|
|
for (const unsubscribe of this._unsubscribers) {
|
|
unsubscribe();
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
};
|
|
export {
|
|
AsyncCache,
|
|
cacheFunction
|
|
};
|
|
//# sourceMappingURL=caches.js.map
|