Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@soundxyz/fine-grained-cache

Package Overview
Dependencies
Maintainers
11
Versions
15
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@soundxyz/fine-grained-cache - npm Package Compare versions

Comparing version 3.0.1 to 3.1.0

67

dist/fineGrained.d.ts

@@ -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 {};

331

dist/fineGrained.js

@@ -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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc