layered-loader
Version:
Data loader with support for caching and fallback data sources
157 lines • 7.27 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.RedisGroupCache = void 0;
const GroupLoader_1 = require("../GroupLoader");
const AbstractRedisCache_1 = require("./AbstractRedisCache");
const RedisExpirationTimeGroupDataSource_1 = require("./RedisExpirationTimeGroupDataSource");
const lua_1 = require("./lua");
const GROUP_INDEX_KEY = 'group-index';
class RedisGroupCache extends AbstractRedisCache_1.AbstractRedisCache {
expirationTimeLoadingGroupedOperation;
ttlLeftBeforeRefreshInMsecs;
name = 'Redis group cache';
constructor(redis, config = {}) {
super(redis, config);
this.ttlLeftBeforeRefreshInMsecs = config.ttlLeftBeforeRefreshInMsecs;
this.redis.defineCommand('getOrSetZeroWithTtl', {
lua: lua_1.GET_OR_SET_ZERO_WITH_TTL,
numberOfKeys: 1,
});
this.redis.defineCommand('getOrSetZeroWithoutTtl', {
lua: lua_1.GET_OR_SET_ZERO_WITHOUT_TTL,
numberOfKeys: 1,
});
if (!this.ttlLeftBeforeRefreshInMsecs && config.ttlCacheTtl) {
throw new Error('ttlCacheTtl cannot be specified if ttlLeftBeforeRefreshInMsecs is not.');
}
this.expirationTimeLoadingGroupedOperation = new GroupLoader_1.GroupLoader({
inMemoryCache: config.ttlCacheTtl
? {
cacheId: 'expiration-time-loading-cache',
ttlInMsecs: config.ttlCacheTtl,
maxGroups: config.ttlCacheGroupSize ?? 200,
maxItemsPerGroup: config.ttlCacheSize ?? 500,
}
: undefined,
dataSources: [new RedisExpirationTimeGroupDataSource_1.RedisExpirationTimeGroupDataSource(this)],
});
}
async deleteGroup(group) {
const key = this.resolveGroupIndexPrefix(group);
if (this.config.ttlInMsecs) {
await this.redis.multi().incr(key).pexpire(key, this.config.ttlInMsecs).exec();
return;
}
return this.redis.incr(key);
}
async deleteFromGroup(key, group) {
const currentGroupKey = await this.redis.get(this.resolveGroupIndexPrefix(group));
if (!currentGroupKey) {
return;
}
await this.redis.del(this.resolveKeyWithGroup(key, group, currentGroupKey));
}
async getFromGroup(key, groupId) {
const currentGroupKey = await this.redis.get(this.resolveGroupIndexPrefix(groupId));
if (!currentGroupKey) {
return undefined;
}
const redisResult = await this.redis.get(this.resolveKeyWithGroup(key, groupId, currentGroupKey));
return this.postprocessResult(redisResult);
}
async getManyFromGroup(keys, groupId) {
const currentGroupKey = await this.redis.get(this.resolveGroupIndexPrefix(groupId));
if (!currentGroupKey) {
return {
resolvedValues: [],
unresolvedKeys: keys,
};
}
const transformedKeys = keys.map((key) => this.resolveKeyWithGroup(key, groupId, currentGroupKey));
const resolvedValues = [];
const unresolvedKeys = [];
return this.redis.mget(transformedKeys).then((redisResult) => {
for (let i = 0; i < keys.length; i++) {
const currentResult = redisResult[i];
if (currentResult !== null) {
resolvedValues.push(this.postprocessResult(currentResult));
}
else {
unresolvedKeys.push(keys[i]);
}
}
return {
resolvedValues,
unresolvedKeys,
};
});
}
async getExpirationTimeFromGroup(key, groupId) {
const now = Date.now();
const currentGroupKey = await this.redis.get(this.resolveGroupIndexPrefix(groupId));
if (currentGroupKey === null) {
return undefined;
}
const remainingTtl = await this.redis.pttl(this.resolveKeyWithGroup(key, groupId, currentGroupKey));
return remainingTtl && remainingTtl > 0 ? now + remainingTtl : undefined;
}
async setForGroup(key, value, groupId) {
const getGroupKeyPromise = this.config.groupTtlInMsecs
? // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.redis.getOrSetZeroWithTtl(this.resolveGroupIndexPrefix(groupId), this.config.groupTtlInMsecs)
: // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.redis.getOrSetZeroWithoutTtl(this.resolveGroupIndexPrefix(groupId));
const currentGroupKey = await getGroupKeyPromise;
const entryKey = this.resolveKeyWithGroup(key, groupId, currentGroupKey);
await this.internalSet(entryKey, value);
if (this.ttlLeftBeforeRefreshInMsecs) {
void this.expirationTimeLoadingGroupedOperation.invalidateCacheFor(key, groupId);
}
}
async setManyForGroup(entries, groupId) {
const getGroupKeyPromise = this.config.groupTtlInMsecs
? // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.redis.getOrSetZeroWithTtl(this.resolveGroupIndexPrefix(groupId), this.config.groupTtlInMsecs)
: // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.redis.getOrSetZeroWithoutTtl(this.resolveGroupIndexPrefix(groupId));
const currentGroupKey = await getGroupKeyPromise;
if (this.config.ttlInMsecs) {
const setCommands = [];
for (let i = 0; i < entries.length; i++) {
const entry = entries[i];
setCommands.push([
'set',
this.resolveKeyWithGroup(entry.key, groupId, currentGroupKey),
entry.value && this.config.json ? JSON.stringify(entry.value) : entry.value,
'PX',
this.config.ttlInMsecs,
]);
}
return this.redis.multi(setCommands).exec();
}
// No TTL set
const commandParam = [];
for (let i = 0; i < entries.length; i++) {
const entry = entries[i];
commandParam.push(this.resolveKeyWithGroup(entry.key, groupId, currentGroupKey));
commandParam.push(entry.value && this.config.json ? JSON.stringify(entry.value) : entry.value);
}
return this.redis.mset(commandParam);
}
resolveKeyWithGroup(key, groupId, groupIndexKey) {
return `${this.config.prefix}${this.config.separator}${groupId}${this.config.separator}${groupIndexKey}${this.config.separator}${key}`;
}
resolveGroupIndexPrefix(groupId) {
return `${this.config.prefix}${this.config.separator}${GROUP_INDEX_KEY}${this.config.separator}${groupId}`;
}
async close() {
// prevent refreshes after everything is shutting down to prevent "Error: Connection is closed." errors
this.ttlLeftBeforeRefreshInMsecs = 0;
}
}
exports.RedisGroupCache = RedisGroupCache;
//# sourceMappingURL=RedisGroupCache.js.map