@soundxyz/fine-grained-cache
Advanced tools
Comparing version 3.0.1 to 3.1.0
@@ -23,2 +23,18 @@ import type { Redis } from "ioredis"; | ||
}) => T; | ||
export type StaleWhileRevalidateCallback<T> = (options: { | ||
setTTL(options: { | ||
/** | ||
* Set TTL to `null` to disable updating revalidation time | ||
*/ | ||
revalidationTTL?: StringValue | null; | ||
/** | ||
* Set TTL to `null` to disable caching | ||
*/ | ||
dataTTL?: StringValue | "Infinity" | null; | ||
}): void; | ||
getTTL(): { | ||
revalidationTTL: StringValue | null; | ||
dataTTL: StringValue | "Infinity" | null; | ||
}; | ||
}) => T; | ||
export declare const Events: { | ||
@@ -35,2 +51,4 @@ readonly REDIS_GET: "REDIS_GET"; | ||
readonly PIPELINED_REDIS_SET: "PIPELINED_REDIS_SET"; | ||
readonly STALE_REVALIDATION_CHECK: "STALE_REVALIDATION_CHECK"; | ||
readonly STALE_BACKGROUND_REVALIDATION: "STALE_BACKGROUND_REVALIDATION"; | ||
readonly REDLOCK_ACQUIRED: "REDLOCK_ACQUIRED"; | ||
@@ -48,3 +66,3 @@ readonly REDLOCK_RELEASED: "REDLOCK_RELEASED"; | ||
export type LoggedEvents = Partial<Record<Events, string | boolean | null | ((args: LogEventArgs) => void)>>; | ||
export declare function FineGrainedCache({ redis, redLock: redLockConfig, keyPrefix, memoryCache, onError, logEvents, GETRedisTimeout, pipelineRedisGET, pipelineRedisSET, defaultUseMemoryCache, awaitRedisSet, }: { | ||
export declare function FineGrainedCache<KeyPrefix extends string = "fine-cache-v1">({ redis, redLock: redLockConfig, keyPrefix, memoryCache, onError, logEvents, GETRedisTimeout, pipelineRedisGET, pipelineRedisSET, defaultUseMemoryCache, awaitRedisSet, }: { | ||
redis: Redis; | ||
@@ -60,3 +78,3 @@ redLock?: { | ||
}; | ||
keyPrefix?: string; | ||
keyPrefix?: KeyPrefix; | ||
memoryCache?: MemoryCache<unknown>; | ||
@@ -128,14 +146,39 @@ onError?: (err: unknown) => void; | ||
}) => Awaited<T> | Promise<Awaited<T>>; | ||
getStaleWhileRevalidate: <T_1>(cb: StaleWhileRevalidateCallback<T_1>, { revalidationTTL, dataTTL, keys, forceUpdate, }: { | ||
/** | ||
* TTL that sets how frequent revalidations are executed | ||
*/ | ||
revalidationTTL: StringValue; | ||
/** | ||
* Expiration of the data | ||
* Expected to be long-lived for stale-while-revalidate mechanism to work as expected | ||
* | ||
* @default "Infinity" | ||
*/ | ||
dataTTL?: StringValue | "Infinity" | undefined; | ||
/** | ||
* Unique key combination | ||
*/ | ||
keys: string | [string, ...(string | number)[]]; | ||
/** | ||
* @default false | ||
*/ | ||
forceUpdate?: boolean | undefined; | ||
}) => Promise<Awaited<T_1>>; | ||
generateCacheKey: (keys: string | [string, ...(string | number)[]]) => string; | ||
keyPrefix: string; | ||
generateSWRDataKey: (keys: string | [string, ...(string | number)[]]) => string; | ||
keyPrefix: KeyPrefix; | ||
swrKeyPrefix: `${KeyPrefix}-swr`; | ||
memoryCache: MemoryCache<unknown>; | ||
invalidateCache: (keys_0: string, ...keys_1: (string | number)[]) => Promise<void>; | ||
setCache: <T_1 = unknown>({ populateMemoryCache, ttl, keys, value, }: { | ||
setCache: <T_2 = unknown>({ populateMemoryCache, ttl, keys, value, swr, }: { | ||
populateMemoryCache: boolean; | ||
ttl: StringValue | "Infinity"; | ||
keys: string | [string, ...(string | number)[]]; | ||
value: T_1; | ||
value: T_2; | ||
swr?: boolean | undefined; | ||
}) => Promise<void>; | ||
readCache: <T_2 = unknown>({ keys }: { | ||
readCache: <T_3 = unknown>({ keys, swr, }: { | ||
keys: string | [string, ...(string | number)[]]; | ||
swr?: boolean | undefined; | ||
}) => Promise<{ | ||
@@ -146,5 +189,15 @@ readonly found: false; | ||
readonly found: true; | ||
readonly value: Awaited<T_2>; | ||
readonly value: Awaited<T_3>; | ||
}>; | ||
getRedisValue: (key: string) => Promise<string | null>; | ||
setRedisValue: ({ key, value, ttl, nx, }: { | ||
key: string; | ||
value: string; | ||
ttl?: number | undefined; | ||
nx?: boolean | undefined; | ||
}) => Promise<unknown>; | ||
clearRedisValues: ({ keys }: { | ||
keys: string | Array<string>; | ||
}) => Promise<number>; | ||
}; | ||
export {}; |
@@ -13,3 +13,2 @@ 'use strict'; | ||
const lruCache__default = /*#__PURE__*/_interopDefaultLegacy(lruCache); | ||
const ms__default = /*#__PURE__*/_interopDefaultLegacy(ms); | ||
@@ -27,11 +26,2 @@ const superjson__default = /*#__PURE__*/_interopDefaultLegacy(superjson); | ||
const NotFoundSymbol = Symbol.for("CacheNotFound"); | ||
const ConcurrentLoadingCache = {}; | ||
function ConcurrentCachedCall(key, cb) { | ||
const concurrentLoadingValueCache = ConcurrentLoadingCache[key]; | ||
if (concurrentLoadingValueCache) | ||
return concurrentLoadingValueCache; | ||
return (ConcurrentLoadingCache[key] = cb()).finally(() => { | ||
delete ConcurrentLoadingCache[key]; | ||
}); | ||
} | ||
const Events = { | ||
@@ -48,2 +38,4 @@ REDIS_GET: "REDIS_GET", | ||
PIPELINED_REDIS_SET: "PIPELINED_REDIS_SET", | ||
STALE_REVALIDATION_CHECK: "STALE_REVALIDATION_CHECK", | ||
STALE_BACKGROUND_REVALIDATION: "STALE_BACKGROUND_REVALIDATION", | ||
REDLOCK_ACQUIRED: "REDLOCK_ACQUIRED", | ||
@@ -60,3 +52,3 @@ REDLOCK_RELEASED: "REDLOCK_RELEASED", | ||
keyPrefix = "fine-cache-v1", | ||
memoryCache = new lruCache__default["default"]({ | ||
memoryCache = new lruCache.LRUCache({ | ||
max: 1e3, | ||
@@ -73,2 +65,11 @@ ttl: ms__default["default"]("2 seconds") | ||
}) { | ||
const ConcurrentLoadingCache = {}; | ||
function ConcurrentCachedCall(key, cb) { | ||
const concurrentLoadingValueCache = ConcurrentLoadingCache[key]; | ||
if (concurrentLoadingValueCache) | ||
return concurrentLoadingValueCache; | ||
return (ConcurrentLoadingCache[key] = cb()).finally(() => { | ||
delete ConcurrentLoadingCache[key]; | ||
}); | ||
} | ||
const redLock = redLockConfig?.client; | ||
@@ -107,17 +108,22 @@ const defaultMaxExpectedTime = redLockConfig?.maxExpectedTime || "5 seconds"; | ||
} | ||
const swrKeyPrefix = `${keyPrefix}-swr`; | ||
function generateSWRDataKey(keys) { | ||
return (typeof keys === "string" ? swrKeyPrefix + ":" + keys : swrKeyPrefix + ":" + keys.join(":").replaceAll("*:", "*").replaceAll(":*", "*")).toLowerCase(); | ||
} | ||
const swrRevalidationValue = "1"; | ||
let pendingRedisGets = []; | ||
let pendingRedisTimeout; | ||
let pendingRedisGetTimeout; | ||
function pipelinedRedisGet(key) { | ||
if (pendingRedisTimeout !== void 0) { | ||
clearTimeout(pendingRedisTimeout); | ||
if (pendingRedisGetTimeout !== void 0) { | ||
clearTimeout(pendingRedisGetTimeout); | ||
} | ||
if (typeof pipelineRedisGET === "number" && pendingRedisGets.length >= pipelineRedisGET) { | ||
executePipeline(); | ||
executeGetPipeline(); | ||
} | ||
const promise = utils.createDeferredPromise(); | ||
pendingRedisGets.push([key, promise]); | ||
pendingRedisTimeout = setTimeout(executePipeline); | ||
pendingRedisGetTimeout = setTimeout(executeGetPipeline); | ||
return promise.promise; | ||
async function executePipeline() { | ||
pendingRedisTimeout = void 0; | ||
async function executeGetPipeline() { | ||
pendingRedisGetTimeout = void 0; | ||
const size = pendingRedisGets.length; | ||
@@ -173,3 +179,8 @@ const { promises, commands } = pendingRedisGets.reduce( | ||
let pendingRedisSetTimeout; | ||
function pipelinedRedisSet({ key, value, ttl }) { | ||
function pipelinedRedisSet({ | ||
key, | ||
value, | ||
ttl, | ||
nx | ||
}) { | ||
if (pendingRedisSetTimeout !== void 0) { | ||
@@ -179,3 +190,3 @@ clearTimeout(pendingRedisSetTimeout); | ||
if (typeof pipelineRedisSET === "number" && pendingRedisSets.length >= pipelineRedisSET) { | ||
executePipeline(); | ||
executeSetPipeline(); | ||
} | ||
@@ -187,11 +198,12 @@ const promise = utils.createDeferredPromise(); | ||
ttl, | ||
value | ||
value, | ||
nx | ||
}); | ||
pendingRedisSetTimeout = setTimeout(executePipeline); | ||
pendingRedisSetTimeout = setTimeout(executeSetPipeline); | ||
return promise.promise; | ||
async function executePipeline() { | ||
async function executeSetPipeline() { | ||
pendingRedisSetTimeout = void 0; | ||
const size = pendingRedisSets.length; | ||
const { promises, commands } = pendingRedisSets.reduce( | ||
(acc, { key: key2, promise: promise2, ttl: ttl2, value: value2 }, index) => { | ||
(acc, { key: key2, promise: promise2, ttl: ttl2, value: value2, nx: nx2 }, index) => { | ||
acc.promises[index] = { | ||
@@ -203,2 +215,10 @@ promise: promise2, | ||
}; | ||
if (nx2) { | ||
if (ttl2 != null) { | ||
acc.commands[index] = ["set", key2, value2, "EX", ttl2, "NX"]; | ||
} else { | ||
acc.commands[index] = ["set", key2, value2, "NX"]; | ||
} | ||
return acc; | ||
} | ||
if (ttl2 != null) { | ||
@@ -232,3 +252,3 @@ acc.commands[index] = ["setex", key2, ttl2, value2]; | ||
if (!result) { | ||
promise2.resolve(); | ||
promise2.resolve(null); | ||
} else { | ||
@@ -238,3 +258,3 @@ if (result[0]) { | ||
} else { | ||
promise2.resolve(); | ||
promise2.resolve(result[1]); | ||
} | ||
@@ -260,3 +280,3 @@ } | ||
} | ||
async function getRedisCacheValue(key, checkShortMemoryCache) { | ||
async function getRedisValue(key) { | ||
const tracing = enabledLogEvents?.REDIS_GET || enabledLogEvents?.REDIS_GET_TIMED_OUT ? getTracing() : null; | ||
@@ -292,4 +312,39 @@ let timedOut = void 0; | ||
} | ||
return NotFoundSymbol; | ||
return null; | ||
} | ||
return redisValue; | ||
} catch (err) { | ||
onError(err); | ||
return null; | ||
} | ||
} | ||
function setRedisValue({ | ||
key, | ||
value, | ||
ttl, | ||
nx = false | ||
}) { | ||
if (pipelineRedisSET) { | ||
return pipelinedRedisSet({ | ||
key, | ||
value, | ||
ttl, | ||
nx | ||
}); | ||
} | ||
if (nx) { | ||
if (ttl != null) | ||
return redis.set(key, value, "EX", ttl, "NX"); | ||
return redis.set(key, value, "NX"); | ||
} | ||
if (ttl != null) | ||
return redis.setex(key, ttl, value); | ||
return redis.set(key, value); | ||
} | ||
function clearRedisValues({ keys }) { | ||
return redis.del(Array.isArray(keys) ? keys : [keys]); | ||
} | ||
async function getRedisCacheValue(key, checkShortMemoryCache) { | ||
try { | ||
const redisValue = await getRedisValue(key); | ||
if (redisValue != null) { | ||
@@ -427,53 +482,161 @@ let parsedRedisValue; | ||
const stringifiedValue = superjson__default["default"].stringify(newValue); | ||
if (expirySeconds > 0) { | ||
if (pipelineRedisSET) { | ||
const set = pipelinedRedisSet({ | ||
key, | ||
if (expirySeconds > 0 || ttl === "Infinity") { | ||
const tracing2 = enabledLogEvents?.REDIS_SET && !pipelineRedisSET ? getTracing() : null; | ||
const set = setRedisValue({ | ||
key, | ||
value: stringifiedValue, | ||
ttl: ttl !== "Infinity" ? expirySeconds : void 0 | ||
}).then(() => { | ||
if (tracing2) { | ||
logMessage("REDIS_SET", { | ||
key, | ||
expirySeconds, | ||
timedInvalidationDate: timedInvalidationDate?.toISOString(), | ||
time: tracing2() | ||
}); | ||
} | ||
}).catch(onError); | ||
if (awaitRedisSet || forceUpdate) | ||
await set; | ||
} else if (enabledLogEvents?.REDIS_SKIP_SET) { | ||
logMessage("REDIS_SKIP_SET", { | ||
key, | ||
timedInvalidationDate: timedInvalidationDate?.toISOString() | ||
}); | ||
} | ||
} catch (err) { | ||
onError(err); | ||
} | ||
if (expirySeconds > 0 && checkShortMemoryCache) | ||
memoryCache.set(key, newValue); | ||
return newValue; | ||
} | ||
}); | ||
} | ||
function getStaleWhileRevalidate(cb, { | ||
revalidationTTL, | ||
dataTTL = "Infinity", | ||
keys, | ||
forceUpdate = false | ||
}) { | ||
const dataKey = generateSWRDataKey(keys); | ||
const revalidationKey = generateCacheKey(keys); | ||
return ConcurrentCachedCall(dataKey, async () => { | ||
if (forceUpdate) | ||
return getNewValue(); | ||
const [redisValue, isStale] = await Promise.all([ | ||
getRedisCacheValue(dataKey, false), | ||
getRedisValue(revalidationKey).then((value) => value == null) | ||
]); | ||
if (redisValue !== NotFoundSymbol) { | ||
if (isStale) { | ||
const staleCheckTracing = logEvents?.events.STALE_REVALIDATION_CHECK ? getTracing() : null; | ||
setRedisValue({ | ||
key: revalidationKey, | ||
value: swrRevalidationValue, | ||
ttl: getExpirySeconds(revalidationTTL), | ||
// NX so that only 1 instance can do the revalidation at a time | ||
nx: true | ||
}).then((value) => { | ||
const instanceOwnsRevalidation = value != null; | ||
if (staleCheckTracing) { | ||
logMessage("STALE_REVALIDATION_CHECK", { | ||
dataKey, | ||
revalidationKey, | ||
time: staleCheckTracing(), | ||
shouldRevalidate: instanceOwnsRevalidation | ||
}); | ||
} | ||
return instanceOwnsRevalidation; | ||
}).catch((err) => { | ||
if (staleCheckTracing) { | ||
logMessage("STALE_REVALIDATION_CHECK", { | ||
error: true, | ||
dataKey, | ||
revalidationKey, | ||
time: staleCheckTracing(), | ||
shouldRevalidate: false | ||
}); | ||
} | ||
onError(err); | ||
return false; | ||
}).then((shouldRevalidate) => { | ||
if (!shouldRevalidate) | ||
return; | ||
const backgroundRevalidationTracing = logEvents?.events.STALE_BACKGROUND_REVALIDATION ? getTracing() : null; | ||
getNewValue().then(() => { | ||
if (backgroundRevalidationTracing) { | ||
logMessage("STALE_BACKGROUND_REVALIDATION", { | ||
dataKey, | ||
revalidationKey, | ||
time: backgroundRevalidationTracing() | ||
}); | ||
} | ||
}).catch(onError); | ||
}); | ||
} | ||
return redisValue; | ||
} | ||
return getNewValue(); | ||
async function getNewValue() { | ||
let currentRevalidationTTL = revalidationTTL; | ||
let currentDataTTL = dataTTL; | ||
const tracing = enabledLogEvents?.EXECUTION_TIME ? getTracing() : null; | ||
const newValue = await cb({ | ||
setTTL(options) { | ||
currentRevalidationTTL = options.revalidationTTL !== void 0 ? options.revalidationTTL : currentRevalidationTTL; | ||
currentDataTTL = options.dataTTL !== void 0 ? options.dataTTL : currentDataTTL; | ||
}, | ||
getTTL() { | ||
return { | ||
revalidationTTL: currentRevalidationTTL, | ||
dataTTL: currentDataTTL | ||
}; | ||
} | ||
}); | ||
if (tracing) { | ||
logMessage("EXECUTION_TIME", { | ||
key: dataKey, | ||
time: tracing() | ||
}); | ||
} | ||
try { | ||
const revalidationTtlSeconds = currentRevalidationTTL == null ? 0 : getExpirySeconds(currentRevalidationTTL); | ||
const dataTtlSeconds = currentDataTTL == null ? 0 : currentDataTTL === "Infinity" ? Infinity : getExpirySeconds(currentDataTTL); | ||
const stringifiedValue = superjson__default["default"].stringify(newValue); | ||
if (dataTtlSeconds > 0) { | ||
const tracing2 = enabledLogEvents?.REDIS_SET && !pipelineRedisSET ? getTracing() : null; | ||
const set = Promise.all([ | ||
setRedisValue({ | ||
key: dataKey, | ||
value: stringifiedValue, | ||
ttl: expirySeconds | ||
}).catch(onError); | ||
if (awaitRedisSet || forceUpdate) | ||
await set; | ||
} else { | ||
const tracing2 = enabledLogEvents?.REDIS_SET ? getTracing() : null; | ||
const set = redis.setex(key, expirySeconds, stringifiedValue).then(() => { | ||
ttl: dataTtlSeconds !== Infinity ? dataTtlSeconds : void 0 | ||
}).then(() => { | ||
if (tracing2) { | ||
logMessage("REDIS_SET", { | ||
key, | ||
expirySeconds, | ||
timedInvalidationDate: timedInvalidationDate?.toISOString(), | ||
key: dataKey, | ||
expirySeconds: dataTtlSeconds, | ||
time: tracing2() | ||
}); | ||
} | ||
}).catch(onError); | ||
if (awaitRedisSet || forceUpdate) | ||
await set; | ||
} | ||
} else if (ttl === "Infinity") { | ||
if (pipelineRedisSET) { | ||
const set = pipelinedRedisSet({ | ||
key, | ||
value: stringifiedValue | ||
}).catch(onError); | ||
if (awaitRedisSet || forceUpdate) | ||
await set; | ||
} else { | ||
const tracing2 = enabledLogEvents?.REDIS_SET ? getTracing() : null; | ||
const set = redis.set(key, stringifiedValue).then(() => { | ||
}).catch(onError), | ||
revalidationTtlSeconds > 0 && setRedisValue({ | ||
key: revalidationKey, | ||
value: swrRevalidationValue, | ||
ttl: revalidationTtlSeconds | ||
}).then(() => { | ||
if (tracing2) { | ||
logMessage("REDIS_SET", { | ||
key, | ||
expirySeconds: "Infinity", | ||
timedInvalidationDate: timedInvalidationDate?.toISOString(), | ||
key: revalidationKey, | ||
expirySeconds: revalidationTtlSeconds, | ||
time: tracing2() | ||
}); | ||
} | ||
}).catch(onError); | ||
if (awaitRedisSet || forceUpdate) | ||
await set; | ||
} | ||
}).catch(onError) | ||
]); | ||
if (awaitRedisSet || forceUpdate) | ||
await set; | ||
} else if (enabledLogEvents?.REDIS_SKIP_SET) { | ||
logMessage("REDIS_SKIP_SET", { | ||
key, | ||
timedInvalidationDate: timedInvalidationDate?.toISOString() | ||
key: dataKey | ||
}); | ||
@@ -484,4 +647,2 @@ } | ||
} | ||
if (expirySeconds > 0 && checkShortMemoryCache) | ||
memoryCache.set(key, newValue); | ||
return newValue; | ||
@@ -510,3 +671,3 @@ } | ||
const tracing = enabledLogEvents?.INVALIDATED_KEYS ? getTracing() : null; | ||
await redis.del(keysToInvalidate); | ||
await clearRedisValues({ keys: keysToInvalidate }); | ||
if (tracing) { | ||
@@ -525,5 +686,6 @@ logMessage("INVALIDATED_KEYS", { | ||
keys, | ||
value | ||
value, | ||
swr | ||
}) { | ||
const key = generateCacheKey(keys); | ||
const key = swr ? generateSWRDataKey(keys) : generateCacheKey(keys); | ||
const expirySeconds = ttl === "Infinity" ? -1 : getExpirySeconds(ttl); | ||
@@ -535,3 +697,3 @@ const stringifiedValue = superjson__default["default"].stringify(value); | ||
if (pipelineRedisSET) { | ||
await pipelinedRedisSet({ | ||
await setRedisValue({ | ||
key, | ||
@@ -543,3 +705,7 @@ value: stringifiedValue, | ||
const tracing = enabledLogEvents?.REDIS_SET ? getTracing() : null; | ||
await redis.setex(key, expirySeconds, stringifiedValue).then(() => { | ||
await setRedisValue({ | ||
key, | ||
value: stringifiedValue, | ||
ttl: expirySeconds | ||
}).then(() => { | ||
if (tracing) { | ||
@@ -558,3 +724,3 @@ logMessage("REDIS_SET", { | ||
if (pipelineRedisSET) { | ||
await pipelinedRedisSet({ | ||
await setRedisValue({ | ||
key, | ||
@@ -565,3 +731,3 @@ value: stringifiedValue | ||
const tracing = enabledLogEvents?.REDIS_SET ? getTracing() : null; | ||
await redis.set(key, stringifiedValue).then(() => { | ||
await setRedisValue({ key, value: stringifiedValue }).then(() => { | ||
if (tracing) { | ||
@@ -578,4 +744,7 @@ logMessage("REDIS_SET", { | ||
} | ||
function readCache({ keys }) { | ||
const key = generateCacheKey(keys); | ||
function readCache({ | ||
keys, | ||
swr | ||
}) { | ||
const key = swr ? generateSWRDataKey(keys) : generateCacheKey(keys); | ||
return getRedisCacheValue(key, false).then((value) => { | ||
@@ -595,8 +764,14 @@ if (value === NotFoundSymbol) { | ||
getCached, | ||
getStaleWhileRevalidate, | ||
generateCacheKey, | ||
generateSWRDataKey, | ||
keyPrefix, | ||
swrKeyPrefix, | ||
memoryCache, | ||
invalidateCache, | ||
setCache, | ||
readCache | ||
readCache, | ||
getRedisValue, | ||
setRedisValue, | ||
clearRedisValues | ||
}; | ||
@@ -603,0 +778,0 @@ } |
{ | ||
"name": "@soundxyz/fine-grained-cache", | ||
"version": "3.0.1", | ||
"version": "3.1.0", | ||
"description": "Fine-grained cache helper using redis", | ||
@@ -34,21 +34,22 @@ "keywords": [ | ||
"dependencies": { | ||
"lru-cache": "^7.14.1", | ||
"lru-cache": "^9.1.0", | ||
"ms": "3.0.0-canary.1", | ||
"superjson": "^1.12.1" | ||
"superjson": "^1.12.2" | ||
}, | ||
"devDependencies": { | ||
"@changesets/cli": "^2.26.0", | ||
"@types/node": "^18.11.18", | ||
"ava": "^5.1.1", | ||
"@changesets/cli": "^2.26.1", | ||
"@types/node": "^18.15.11", | ||
"ava": "^5.2.0", | ||
"bob-ts": "^4.1.1", | ||
"bob-tsm": "^1.1.2", | ||
"c8": "^7.12.0", | ||
"concurrently": "^7.6.0", | ||
"c8": "^7.13.0", | ||
"concurrently": "^8.0.1", | ||
"date-fns": "^2.29.3", | ||
"esbuild": "^0.16.17", | ||
"execa": "^6.1.0", | ||
"ioredis": "^5.2.4", | ||
"prettier": "^2.8.2", | ||
"esbuild": "^0.17.17", | ||
"execa": "^7.1.1", | ||
"ioredis": "^5.3.2", | ||
"prettier": "^2.8.7", | ||
"redlock": "5.0.0-beta.2", | ||
"typescript": "^4.9.4" | ||
"typescript": "^5.0.4", | ||
"wait-for-expect": "^3.0.2" | ||
}, | ||
@@ -55,0 +56,0 @@ "peerDependencies": { |
@@ -1,1 +0,3 @@ | ||
# fine-cache | ||
# fine-cache | ||
Already open source on public npm : https://www.npmjs.com/package/@soundxyz/fine-grained-cache |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
63476
1823
4
15
+ Addedlru-cache@9.1.2(transitive)
- Removedlru-cache@7.18.3(transitive)
Updatedlru-cache@^9.1.0
Updatedsuperjson@^1.12.2