Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allowing TTL to be set with a function as well #1

Merged
merged 1 commit into from
Oct 1, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ const client = new NodeCache();
useNodeCacheAdapter(client);
```

### Change Global Options
Some options can be configured globally for all decorated methods. Here is an example of how you can change these options:
```ts
// Import and set adapter as above
client.setOptions(<CacheManagerOptions> {
excludeContext: false, // Defaults to true. If you don't pass a specific hashKey into the decorators, one will be generated by serializing the arguments passed in and optionally the context of the instance the method is being called on.
ttlSeconds: 0, // A global setting for the number of seconds the decorated method's results will be cached for.
});
```

Currently, there are two decorators available in this library: `@Cacheable` and `@CacheClear`. Here is a sample of how they can be used:

```ts
Expand All @@ -51,7 +61,7 @@ class TestClass {

private userRepository: Repository<User>;

// This function is being called to generate a cache key based on the given arguments.
// This static method is being called to generate a cache key based on the given arguments.
// Not featured here: the second argument, context, which is the instance the method
// was called on.
static setCacheKey = (args: any[]) => args[0];
Expand All @@ -64,9 +74,9 @@ class TestClass {
}

// If getProp('123') were called, the return value would be cached
// under 123 in this case.
@Cacheable({ cacheKey: TestClass.setCacheKey })
public async getProp(id: string): Promise<any> {
// under 123 in this case for 10 seconds
@Cacheable({ cacheKey: TestClass.setCacheKey, ttl: args => args[1] })
public async getProp(id: string, cacheForSeconds: number): Promise<any> {
return this.aProp;
}

Expand All @@ -87,8 +97,8 @@ interface CacheOptions {
cacheKey?: string | CacheKeyBuilder; // Individual key the result of the decorated method should be stored on
hashKey?: string | CacheKeyBuilder; // Set name the result of the decorated method should be stored on (for hashes)
client?: CacheClient; // If you would prefer use a different cache client than passed into the adapter, set that here
ttlSeconds?: number; // Number of seconds the cached key should live for
noop?: boolean; // Allows for consuming libraries to conditionally disable caching. Set this to true to disable caching for some reason.
ttlSeconds?: number | TTLBuilder; // Number of seconds the cached key should live for
}
```

Expand Down
1 change: 1 addition & 0 deletions lib/CacheManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { CacheClient, CacheManagerOptions } from './interfaces';
export default class CacheManager {
public client: CacheClient | null = null;
public options: CacheManagerOptions = {
excludeContext: true,
ttlSeconds: 0,
};

Expand Down
9 changes: 6 additions & 3 deletions lib/decorators/CacheClear.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,16 @@ export function CacheClear(options?: CacheClearOptions) {
throw new MissingClientError(propertyKey);
}

const cacheKey = getCacheKey(options && options.cacheKey, propertyKey, args, this);
const hashKey = extractKey(options && options.hashKey, args, this);
const contextToUse = !cacheManager.options.excludeContext
? this
: undefined;
const cacheKey = getCacheKey(options && options.cacheKey, propertyKey, args, contextToUse);
const hashKey = extractKey(options && options.hashKey, args, contextToUse);
const finalKey = hashKey
? `${hashKey}:${cacheKey}`
: cacheKey;

// Run the decorated function
// Run the decorated method
const result = await descriptor.value!.apply(this, args);

// Delete the requested value from cache
Expand Down
11 changes: 7 additions & 4 deletions lib/decorators/Cacheable.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CacheOptions } from '../interfaces';
import { getCacheKey, extractKey, determineOp } from '../util';
import { determineOp, extractKey, getCacheKey, getTTL } from '../util';
import { MissingClientError } from '../errors';
import cacheManager from '../index';

Expand Down Expand Up @@ -32,8 +32,11 @@ export function Cacheable(options?: CacheOptions) {
throw new MissingClientError(propertyKey);
}

const cacheKey = getCacheKey(options && options.cacheKey, propertyKey, args, this);
const hashKey = extractKey(options && options.hashKey, args, this);
const contextToUse = !cacheManager.options.excludeContext
? this
: undefined;
const cacheKey = getCacheKey(options && options.cacheKey, propertyKey, args, contextToUse);
const hashKey = extractKey(options && options.hashKey, args, contextToUse);
const finalKey = hashKey
? `${hashKey}:${cacheKey}`
: cacheKey;
Expand All @@ -50,7 +53,7 @@ export function Cacheable(options?: CacheOptions) {
// TTL in seconds should prioritize options set in the decorator first,
// the CacheManager options second, and be undefined if unset.
const ttl = options && options.ttlSeconds
? options.ttlSeconds
? getTTL(options.ttlSeconds, args, contextToUse)
: cacheManager.options.ttlSeconds || undefined;

await client.set(finalKey, result, ttl);
Expand Down
1 change: 1 addition & 0 deletions lib/interfaces/CacheManagerOptions.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export interface CacheManagerOptions {
excludeContext?: boolean;
ttlSeconds?: number;
}
6 changes: 2 additions & 4 deletions lib/interfaces/CacheOptions.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { CacheKeyBuilder } from './CacheKeyBuilder';
import { CacheClient } from './CacheClient';
import { NoOpDeterminer } from './NoOpDeterminer';
import { CacheKeyBuilder, CacheClient, NoOpDeterminer, TTLBuilder } from './';

export interface CacheOptions {
cacheKey?: string | CacheKeyBuilder;
hashKey?: string | CacheKeyBuilder;
client?: CacheClient;
ttlSeconds?: number;
noop?: boolean | NoOpDeterminer;
ttlSeconds?: number | TTLBuilder;
}
3 changes: 3 additions & 0 deletions lib/interfaces/TTLBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface TTLBuilder {
(args: any[], context?: any): number;
};
1 change: 1 addition & 0 deletions lib/interfaces/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export { CacheOptions } from './CacheOptions';
export { CacheClearOptions } from './CacheClearOptions';
export { CacheManagerOptions } from './CacheManagerOptions';
export { NoOpDeterminer } from './NoOpDeterminer';
export { TTLBuilder } from './TTLBuilder';
4 changes: 2 additions & 2 deletions lib/util/getCacheKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { CacheKeyBuilder } from '../interfaces';
/**
* extractKey - If data should be stored in a hash, this would be the name of the hash
*
* @param passedInKey The desired key, or function to build the key based on arguments
* @param passedInKey The desired key, or function to build the key based on arguments/context
* @param args The arguments the decorated method was called with
* @param context The instance whose method is being called
*
Expand Down Expand Up @@ -34,7 +34,7 @@ export const extractKey = (passedInKey: string | CacheKeyBuilder = '', args: any
*/
export const getCacheKey = (passedInKey: string | CacheKeyBuilder = '', methodName: string, args: any[], context?: any): string => {
// If the user passed in a cacheKey, use that. If it's a string/number, use it directly.
// In the case of a function
// In the case of a function, we'll use the result of the called function.
if (passedInKey) {
return extractKey(passedInKey, args, context);
}
Expand Down
18 changes: 18 additions & 0 deletions lib/util/getTTL.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { TTLBuilder } from '../interfaces';

/**
* getTTL - This is the TTL in seconds to cache data for, or a function to extract it
*
* @param passedInTTL The desired TTL, or function to build the TTL based on arguments/context
* @param args The arguments the decorated method was called with
* @param context The instance whose method is being called
*
* @returns {String}
*/
export const getTTL = (passedInTTL: number | TTLBuilder, args: any[], context?: any): number => {
// If the user passed in a cacheKey, use that. If it's a string/number, use it directly.
// In the case of a function
return passedInTTL instanceof Function
? passedInTTL(args, context)
: passedInTTL;
};
1 change: 1 addition & 0 deletions lib/util/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { determineOp } from './determineOp';
export { getCacheKey, extractKey, getSeparatedKeys } from './getCacheKey';
export { getTTL } from './getTTL';
export { useRedisAdapter, useNodeCacheAdapter } from './useAdapter';
export { parseIfRequired } from './parseIfRequired';
2 changes: 1 addition & 1 deletion test/util/getCacheKey.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getCacheKey } from '../../lib/util/getCacheKey';
import { getCacheKey } from '../../lib/util';

const mockArgs: any[] = [
1,
Expand Down
22 changes: 22 additions & 0 deletions test/util/getTTL.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { getTTL } from '../../lib/util';

const mockArgs: any[] = [
1,
'two',
function () { console.log('this is a test'); }
];

describe('getTTL Tests', () => {
it('should return the number given as ttlSeconds, if a number is given', () => {
expect(getTTL(10, mockArgs)).toBe(10);
});

it('should return the value returned by a passed in TTLBuilder as ttlSeconds, if a fn is given', () => {
expect(getTTL(
(args) => {
return args.length;
},
mockArgs,
)).toBe(mockArgs.length);
});
});