UNPKG

layered-loader

Version:

Data loader with support for caching and fallback data sources

157 lines 7.27 kB
"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