Skip to content

Commit 4ee8662

Browse files
committed
feat: Add force option to cachedFunction
1 parent 1777538 commit 4ee8662

File tree

5 files changed

+64
-29
lines changed

5 files changed

+64
-29
lines changed

src/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ export type CachedFunctionInitializerOptions =
1212
export type CachedFunctionOptions<F extends AnyFunction> = Partial<CachedFunctionInitializerOptions> & {
1313
selector?: ArgumentPaths<F>;
1414
ttl?: number;
15+
force?: boolean;
1516
};

src/index.ts

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
2+
/* eslint-disable @typescript-eslint/no-explicit-any */
3+
14
import _ from 'lodash';
2-
import {type Cache, caching, type Store} from 'cache-manager';
5+
import {
6+
type Cache, caching, type Store,
7+
} from 'cache-manager';
38
import type {CachedFunctionInitializerOptions, CachedFunctionOptions} from './index.d';
4-
import type {AnyFunction, ArgumentPaths} from './paths.d';
9+
import type {AnyFunction as CacheableFunction, ArgumentPaths} from './paths.d';
510

611
let cache: Cache | undefined;
712

@@ -19,11 +24,14 @@ export async function getOrInitializeCache<S extends Store>(options?: CachedFunc
1924
return cache as Cache<S>;
2025
}
2126

27+
/**
28+
* @deprecated To close any open connections, please retrieve the cache object from `getOrInitializeCache` and close it directly.
29+
*/
2230
export function resetCache() {
2331
cache = undefined;
2432
}
2533

26-
export function selectorToCacheKey<F extends AnyFunction>(arguments_: Parameters<F>, selector: ArgumentPaths<F>) {
34+
export function selectorToCacheKey<F extends CacheableFunction>(arguments_: Parameters<F>, selector: ArgumentPaths<F>) {
2735
const selectors = _.castArray(selector);
2836
if (selectors.length === 0) {
2937
return JSON.stringify(arguments_);
@@ -45,26 +53,47 @@ export function selectorToCacheKey<F extends AnyFunction>(arguments_: Parameters
4553
return JSON.stringify(result);
4654
}
4755

48-
export function cachedFunction<F extends AnyFunction>(function_: F, options: CachedFunctionOptions<F>) {
56+
export function cachedFunction<F extends CacheableFunction>(function_: F, options?: CachedFunctionOptions<F>) {
4957
return async (...arguments_: Parameters<F>): Promise<ReturnType<F>> => {
50-
const selector = options.selector ?? function_.cacheKeys ?? [];
51-
const cacheKey = selectorToCacheKey(arguments_, selector);
58+
const cacheOptions = _.merge({}, options ?? {}, function_.cacheOptions ?? {});
59+
if (_.keys(cacheOptions).length === 0) {
60+
throw new Error('No cache options provided, either use the @CacheOptions decorator or provide options to cachedFunction directly.');
61+
}
62+
63+
const cacheKey = selectorToCacheKey(arguments_, cacheOptions.selector!);
5264
const cache = await getOrInitializeCache(options as CachedFunctionInitializerOptions);
5365

5466
const cacheValue = await cache.get<ReturnType<F>>(cacheKey);
55-
if (cacheValue !== undefined) {
67+
if (!cacheOptions.force && cacheValue !== undefined) {
5668
return cacheValue;
5769
}
5870

5971
const result = await function_(...arguments_) as ReturnType<F>;
60-
await cache.set(cacheKey, result, options.ttl);
72+
await cache.set(cacheKey, result, cacheOptions.ttl);
6173

6274
return result;
6375
};
6476
}
6577

66-
export function cacheKeys<F extends AnyFunction>(function_: F, ...selector: Array<ArgumentPaths<F>>) {
67-
const selectors = _(selector).flatMap().value();
68-
function_.cacheKeys = selectors;
69-
return function_;
78+
// eslint-disable-next-line @typescript-eslint/naming-convention
79+
export function CacheOptions<F extends CacheableFunction>(
80+
selectorOrOptions: ArgumentPaths<F> | CachedFunctionOptions<F>,
81+
ttl?: number,
82+
) {
83+
const options = (_.isArrayLike(selectorOrOptions) || _.isString(selectorOrOptions))
84+
? {selector: selectorOrOptions, ttl}
85+
: selectorOrOptions as CachedFunctionOptions<F>;
86+
87+
return (
88+
_target: any,
89+
_propertyKey: string | symbol,
90+
descriptor: TypedPropertyDescriptor<F>,
91+
): any => {
92+
if (!descriptor.value) {
93+
return;
94+
}
95+
96+
descriptor.value.cacheOptions = options;
97+
return descriptor;
98+
};
7099
}

src/paths.d.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
/* eslint-disable @typescript-eslint/no-unused-vars */
22
/* eslint-disable @typescript-eslint/no-explicit-any */
33

4+
import {type Cache} from 'cache-manager';
45
import _ from 'lodash';
6+
import {type CachedFunctionOptions} from './index.d';
57

68
/**
79
* Represents a type that can either be a single value of type T or an array of type T.
@@ -14,7 +16,7 @@ export type SingleOrArray<T> = T | T[];
1416
* Represents any function that takes any number of arguments and returns any value.
1517
*/
1618
export type AnyFunction = ((...arguments_: any[]) => any) & {
17-
cacheKeys?: string[];
19+
cacheOptions?: CachedFunctionOptions<any>;
1820
};
1921

2022
/**

tests/redis.test.ts

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
1+
/* eslint-disable @typescript-eslint/unbound-method */
12
import {random} from 'lodash';
23
import {type RedisStore, redisStore} from 'cache-manager-ioredis-yet';
3-
import {cachedFunction, getOrInitializeCache} from '../src';
4+
import {cachedFunction, CacheOptions, getOrInitializeCache} from '../src';
5+
6+
const cache = await getOrInitializeCache<RedisStore>({
7+
store: await redisStore({
8+
host: 'localhost',
9+
port: 6379,
10+
}),
11+
});
412

513
type Person = {
614
id?: string;
@@ -12,22 +20,15 @@ type Person = {
1220
};
1321
};
1422

15-
async function createPerson(person: Person) {
16-
console.log('Person created!!!!!');
17-
return person;
23+
class CachedPersonCreator {
24+
@CacheOptions({selector: '0.name', ttl: 1000})
25+
static async createPerson(person: Person) {
26+
console.log('Person created!!!!!');
27+
return person;
28+
}
1829
}
1930

20-
const cache = await getOrInitializeCache<RedisStore>({
21-
store: await redisStore({
22-
host: 'localhost',
23-
port: 6379,
24-
}),
25-
});
26-
const cachedCreatePerson = cachedFunction(createPerson, {
27-
selector: '0.name',
28-
ttl: 1000,
29-
});
30-
31+
const cachedCreatePerson = cachedFunction(CachedPersonCreator.createPerson);
3132
const person = await cachedCreatePerson({
3233
id: random(0, 100_000).toString(),
3334
name: 'Tomer Horowitz',

tsconfig.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
"allowJs": true,
1818
"types": [
1919
"bun-types" // add Bun global
20-
]
20+
],
21+
"emitDecoratorMetadata": true,
22+
"experimentalDecorators": true,
2123
}
2224
}

0 commit comments

Comments
 (0)