@soundxyz/fine-grained-cache
Advanced tools
Comparing version 2.0.1 to 2.1.0
@@ -33,2 +33,3 @@ import type { Redis } from "ioredis"; | ||
readonly PIPELINED_REDIS_GETS: "PIPELINED_REDIS_GETS"; | ||
readonly PIPELINED_REDIS_SET: "PIPELINED_REDIS_SET"; | ||
readonly REDLOCK_ACQUIRED: "REDLOCK_ACQUIRED"; | ||
@@ -46,3 +47,3 @@ readonly REDLOCK_RELEASED: "REDLOCK_RELEASED"; | ||
export declare type LoggedEvents = Partial<Record<Events, string | boolean | null>>; | ||
export declare function FineGrainedCache({ redis, redLock: redLockConfig, keyPrefix, memoryCache, onError, logEvents, GETRedisTimeout, pipelineRedisGET, defaultUseMemoryCache, }: { | ||
export declare function FineGrainedCache({ redis, redLock: redLockConfig, keyPrefix, memoryCache, onError, logEvents, GETRedisTimeout, pipelineRedisGET, pipelineRedisSET, defaultUseMemoryCache, awaitRedisSet, }: { | ||
redis: Redis; | ||
@@ -79,2 +80,8 @@ redLock?: { | ||
/** | ||
* Enable usage of redis pipelines for redis SET. | ||
* | ||
* If "number" is specified, that's the maximum amount of operations to be sent in a single pipeline | ||
*/ | ||
pipelineRedisSET?: boolean | number; | ||
/** | ||
* Should `getCached` use memory cache by default? | ||
@@ -87,2 +94,8 @@ * | ||
defaultUseMemoryCache?: boolean; | ||
/** | ||
* Should `getCached` await the Redis set | ||
* | ||
* @default process.env.NODE_ENV === "test" | ||
*/ | ||
awaitRedisSet?: boolean; | ||
}): { | ||
@@ -89,0 +102,0 @@ getCached: <T>(cb: CachedCallback<T>, { timedInvalidation, ttl, keys, maxExpectedTime, retryLockTime, checkShortMemoryCache, useSuperjson, useRedlock, forceUpdate, }: { |
@@ -45,2 +45,3 @@ 'use strict'; | ||
PIPELINED_REDIS_GETS: "PIPELINED_REDIS_GETS", | ||
PIPELINED_REDIS_SET: "PIPELINED_REDIS_SET", | ||
REDLOCK_ACQUIRED: "REDLOCK_ACQUIRED", | ||
@@ -62,3 +63,5 @@ REDLOCK_RELEASED: "REDLOCK_RELEASED", | ||
pipelineRedisGET, | ||
defaultUseMemoryCache = true | ||
pipelineRedisSET, | ||
defaultUseMemoryCache = true, | ||
awaitRedisSet = process.env.NODE_ENV === "test" | ||
}) { | ||
@@ -132,5 +135,5 @@ const redLock = redLockConfig?.client; | ||
logMessage("PIPELINED_REDIS_GETS", { | ||
size, | ||
keys: commands.map(([, key2]) => key2).join(","), | ||
cache: results?.map(([, result]) => typeof result === "string" ? "HIT" : "MISS").join(",") || "null", | ||
size, | ||
time: tracing() | ||
@@ -159,2 +162,71 @@ }); | ||
} | ||
let pendingRedisSets = []; | ||
let pendingRedisSetTimeout; | ||
function pipelinedRedisSet({ key, value, ttl }) { | ||
if (pendingRedisSetTimeout !== void 0) { | ||
clearTimeout(pendingRedisSetTimeout); | ||
} | ||
if (typeof pipelineRedisSET === "number" && pendingRedisSets.length >= pipelineRedisSET) { | ||
executePipeline(); | ||
} | ||
const promise = utils.createDeferredPromise(); | ||
pendingRedisSets.push({ | ||
key, | ||
promise, | ||
ttl | ||
}); | ||
pendingRedisSetTimeout = setTimeout(executePipeline); | ||
return promise.promise; | ||
async function executePipeline() { | ||
pendingRedisSetTimeout = void 0; | ||
const size = pendingRedisSets.length; | ||
const { promises, commands } = pendingRedisSets.reduce((acc, { key: key2, promise: promise2, ttl: ttl2 }, index) => { | ||
acc.promises[index] = { | ||
promise: promise2, | ||
index, | ||
key: key2, | ||
ttl: ttl2 | ||
}; | ||
if (ttl2 != null) { | ||
acc.commands[index] = ["setex", key2, ttl2, value]; | ||
} else { | ||
acc.commands[index] = ["set", key2, value]; | ||
} | ||
return acc; | ||
}, { | ||
promises: new Array(size), | ||
commands: new Array(size) | ||
}); | ||
const tracing = enabledLogEvents?.PIPELINED_REDIS_SET ? getTracing() : null; | ||
pendingRedisSets = []; | ||
try { | ||
const pipeline = redis.pipeline(commands); | ||
const results = await pipeline.exec(); | ||
if (tracing) { | ||
logMessage("PIPELINED_REDIS_SET", { | ||
size, | ||
keys: promises.map(({ key: key2 }) => key2).join(","), | ||
ttl: promises.map(({ ttl: ttl2 }) => ttl2 ?? -1).join(","), | ||
time: tracing() | ||
}); | ||
} | ||
for (const { promise: promise2, index } of promises) { | ||
const result = results?.[index]; | ||
if (!result) { | ||
promise2.resolve(); | ||
} else { | ||
if (result[0]) { | ||
promise2.reject(result[0]); | ||
} else { | ||
promise2.resolve(); | ||
} | ||
} | ||
} | ||
} catch (err) { | ||
for (const { promise: promise2 } of promises) { | ||
promise2.reject(err); | ||
} | ||
} | ||
} | ||
} | ||
async function getRedisCacheValue(key, useSuperjson, checkShortMemoryCache) { | ||
@@ -315,22 +387,47 @@ const tracing = enabledLogEvents?.REDIS_GET || enabledLogEvents?.REDIS_GET_TIMED_OUT ? getTracing() : null; | ||
if (expirySeconds > 0) { | ||
const tracing2 = enabledLogEvents?.REDIS_SET ? getTracing() : null; | ||
await redis.setex(key, expirySeconds, stringifiedValue); | ||
if (tracing2) { | ||
logMessage("REDIS_SET", { | ||
if (pipelineRedisSET != null) { | ||
const set = pipelinedRedisSet({ | ||
key, | ||
expirySeconds, | ||
timedInvalidationDate: timedInvalidationDate?.toISOString(), | ||
time: tracing2() | ||
}); | ||
value: stringifiedValue, | ||
ttl: expirySeconds | ||
}).catch(onError); | ||
if (awaitRedisSet) | ||
await set; | ||
} else { | ||
const tracing2 = enabledLogEvents?.REDIS_SET ? getTracing() : null; | ||
const set = redis.setex(key, expirySeconds, stringifiedValue).then(() => { | ||
if (tracing2) { | ||
logMessage("REDIS_SET", { | ||
key, | ||
expirySeconds, | ||
timedInvalidationDate: timedInvalidationDate?.toISOString(), | ||
time: tracing2() | ||
}); | ||
} | ||
}).catch(onError); | ||
if (awaitRedisSet) | ||
await set; | ||
} | ||
} else if (ttl === "Infinity") { | ||
const tracing2 = enabledLogEvents?.REDIS_SET ? getTracing() : null; | ||
await redis.set(key, stringifiedValue); | ||
if (tracing2) { | ||
logMessage("REDIS_SET", { | ||
if (pipelineRedisSET != null) { | ||
const set = pipelinedRedisSet({ | ||
key, | ||
expirySeconds: "Infinity", | ||
timedInvalidationDate: timedInvalidationDate?.toISOString(), | ||
time: tracing2() | ||
}); | ||
value: stringifiedValue | ||
}).catch(onError); | ||
if (awaitRedisSet) | ||
await set; | ||
} else { | ||
const tracing2 = enabledLogEvents?.REDIS_SET ? getTracing() : null; | ||
const set = redis.set(key, stringifiedValue).then(() => { | ||
if (tracing2) { | ||
logMessage("REDIS_SET", { | ||
key, | ||
expirySeconds: "Infinity", | ||
timedInvalidationDate: timedInvalidationDate?.toISOString(), | ||
time: tracing2() | ||
}); | ||
} | ||
}).catch(onError); | ||
if (awaitRedisSet) | ||
await set; | ||
} | ||
@@ -337,0 +434,0 @@ } else if (enabledLogEvents?.REDIS_SKIP_SET) { |
{ | ||
"name": "@soundxyz/fine-grained-cache", | ||
"version": "2.0.1", | ||
"version": "2.1.0", | ||
"description": "Fine-grained cache helper using redis", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
Sorry, the diff of this file is not supported yet
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
44383
1207
1
2