layered-loader
Version:
Data loader with support for caching and fallback data sources
130 lines • 6.29 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.GroupLoader = void 0;
const AbstractGroupCache_1 = require("./AbstractGroupCache");
class GroupLoader extends AbstractGroupCache_1.AbstractGroupCache {
dataSources;
groupRefreshFlags;
throwIfLoadError;
throwIfUnresolved;
constructor(config) {
super(config);
this.dataSources = config.dataSources ?? [];
this.throwIfLoadError = config.throwIfLoadError ?? true;
this.throwIfUnresolved = config.throwIfUnresolved ?? false;
this.groupRefreshFlags = new Map();
}
resolveGroupValue(key, group, loadParams) {
return super.resolveGroupValue(key, group).then((cachedValue) => {
if (cachedValue !== undefined) {
if (this.asyncCache?.ttlLeftBeforeRefreshInMsecs) {
let groupSet = this.groupRefreshFlags.get(group);
let isAlreadyRefreshing = groupSet?.has(key);
if (!isAlreadyRefreshing) {
this.asyncCache.expirationTimeLoadingGroupedOperation.get(key, group).then((expirationTime) => {
if (expirationTime && expirationTime - Date.now() < this.asyncCache.ttlLeftBeforeRefreshInMsecs) {
// Check if someone else didn't start refreshing while we were checking expiration time
groupSet = this.groupRefreshFlags.get(group);
isAlreadyRefreshing = groupSet?.has(key);
if (!isAlreadyRefreshing) {
if (!groupSet) {
groupSet = new Set();
this.groupRefreshFlags.set(group, groupSet);
}
groupSet.add(key);
this.loadFromLoaders(key, group, loadParams)
.catch((err) => {
this.logger.error(err.message);
})
.finally(() => {
groupSet.delete(key);
});
}
}
});
}
}
return cachedValue;
}
return this.loadFromLoaders(key, group, loadParams).then((finalValue) => {
if (finalValue !== undefined) {
return finalValue;
}
if (this.throwIfUnresolved) {
throw new Error(`Failed to resolve value for key "${key}", group "${group}"`);
}
return undefined;
});
});
}
async resolveManyGroupValues(keys, group, loadParams) {
// load what is available from async cache
const cachedValues = await super.resolveManyGroupValues(keys, group, loadParams);
// everything was cached, no need to load anything
if (cachedValues.unresolvedKeys.length === 0) {
return cachedValues;
}
const loadValues = await this.loadManyFromLoaders(cachedValues.unresolvedKeys, group, loadParams);
if (this.asyncCache) {
const cacheEntries = loadValues.map((loadValue) => {
return {
key: this.cacheKeyFromValueResolver(loadValue),
value: loadValue,
};
});
await this.asyncCache.setManyForGroup(cacheEntries, group).catch((err) => {
this.cacheUpdateErrorHandler(err, cacheEntries.map((entry) => entry.key).join(', '), this.asyncCache, this.logger);
});
}
return {
resolvedValues: [...cachedValues.resolvedValues, ...loadValues],
// there actually may still be some unresolved keys, but we no longer know that
unresolvedKeys: [],
};
}
async loadFromLoaders(key, group, loadParams) {
for (let index = 0; index < this.dataSources.length; index++) {
const resolvedValue = await this.dataSources[index].getFromGroup(loadParams, group).catch((err) => {
this.loadErrorHandler(err, key, this.dataSources[index], this.logger);
if (this.throwIfLoadError) {
throw err;
}
});
if (resolvedValue !== undefined || index === this.dataSources.length - 1) {
if (resolvedValue === undefined && this.throwIfUnresolved) {
throw new Error(`Failed to resolve value for key "${key}", group "${group}"`);
}
const finalValue = resolvedValue ?? null;
if (this.asyncCache) {
await this.asyncCache.setForGroup(key, finalValue, group).catch((err) => {
this.cacheUpdateErrorHandler(err, key, this.asyncCache, this.logger);
});
}
return finalValue;
}
}
return undefined;
}
async loadManyFromLoaders(keys, group, loadParams) {
let lastResolvedValues;
for (let index = 0; index < this.dataSources.length; index++) {
lastResolvedValues = await this.dataSources[index].getManyFromGroup(keys, group, loadParams).catch((err) => {
this.loadErrorHandler(err, keys.toString(), this.dataSources[index], this.logger);
if (this.throwIfLoadError) {
throw err;
}
return [];
});
if (lastResolvedValues.length === keys.length) {
return lastResolvedValues;
}
}
if (this.throwIfUnresolved) {
throw new Error(`Failed to resolve value for some of the keys (group ${group}): ${keys.join(', ')}`);
}
// ToDo do we want to return results of a query that returned the most amount of entities?
return lastResolvedValues ?? [];
}
}
exports.GroupLoader = GroupLoader;
//# sourceMappingURL=GroupLoader.js.map