Installations
npm install @type-cacheable/core
Developer Guide
Typescript
Yes
Module System
CommonJS
Node Version
20.9.0
NPM Version
lerna/8.1.8/node@v20.9.0+arm64 (darwin)
Releases
Unable to fetch releases
Contributors
Languages
TypeScript (97.66%)
JavaScript (2.29%)
Dockerfile (0.05%)
Love this project? Help keep it running — sponsor us today! 🚀
Developer
joshuaslate
Download Statistics
Total Downloads
2,975,532
Last Day
5,070
Last Week
19,640
Last Month
82,015
Last Year
953,615
GitHub Statistics
181 Stars
1,250 Commits
37 Forks
3 Watching
2 Branches
20 Contributors
Bundle Size
36.93 kB
Minified
9.61 kB
Minified + Gzipped
Package Meta Information
Latest Version
14.1.0
Package Id
@type-cacheable/core@14.1.0
Unpacked Size
92.99 kB
Size
18.35 kB
File Count
135
NPM Version
lerna/8.1.8/node@v20.9.0+arm64 (darwin)
Node Version
20.9.0
Publised On
21 Aug 2024
Total Downloads
Cumulative downloads
Total Downloads
2,975,532
Last day
0.7%
5,070
Compared to previous day
Last week
-8.7%
19,640
Compared to previous week
Last month
12.8%
82,015
Compared to previous month
Last year
10.9%
953,615
Compared to previous year
Daily Downloads
Weekly Downloads
Monthly Downloads
Yearly Downloads
Dependencies
3
Dev Dependencies
4
type-cacheable
TypeScript-based caching decorators to assist with caching (and clearing cache for) async methods. Currently supports Redis (redis
, ioredis
), lru-cache
, and node-cache
. If you would like to see more adapters added, please open an issue or, better yet, a pull request with a working implementation.
Usage
Installation
1npm install --save @type-cacheable/core
or
1yarn add @type-cacheable/core
Setup Adapter
You will need to set up the appropriate adapter for your cache of choice.
Redis:
redis
-@type-cacheable/redis-adapter
- https://github.com/joshuaslate/type-cacheable/tree/main/packages/redis-adapterioredis
-@type-cacheable/ioredis-adapter
- https://github.com/joshuaslate/type-cacheable/tree/main/packages/ioredis-adapter
LRU-Cache
lru-cache
-@type-cacheable/lru-cache-adapter
- https://github.com/joshuaslate/type-cacheable/tree/main/packages/lru-cache-adapter
Node-Cache:
node-cache
-@type-cacheable/node-cache-adapter
https://github.com/joshuaslate/type-cacheable/tree/main/packages/node-cache-adapter
Cache-Manager:
cache-manager
-@type-cacheable/cache-manager-adapter
- https://github.com/joshuaslate/type-cacheable/tree/main/packages/cache-manager-adapter
Change Global Options
Some options can be configured globally for all decorated methods. Here is an example of how you can change these options:
1// Import and set adapter as above
2import cacheManager, { CacheManagerOptions } from '@type-cacheable/core';
3cacheManager.setOptions(<CacheManagerOptions>{
4 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.
5 ttlSeconds: 0, // A global setting for the number of seconds the decorated method's results will be cached for.
6});
Currently, there are three decorators available in this library: @Cacheable
, @CacheClear
, and @CacheUpdate
. Here is a sample of how they can be used:
1import Redis from 'redis'; 2import { Cacheable, CacheClear } from '@type-cacheable/core'; 3 4const userClient = Redis.createClient(); 5const clientAdapter = useAdapter(userClient); 6 7class TestClass { 8 private values: any[] = [1, 2, 3, 4, 5]; 9 private userRepository: Repository<User>; 10 11 // This static method is being called to generate a cache key based on the given arguments. 12 // Not featured here: the second argument, context, which is the instance the method 13 // was called on. 14 static setCacheKey = (args: any[]) => args[0]; 15 16 @Cacheable({ cacheKey: 'values' }) 17 public getValues(): Promise<number[]> { 18 return Promise.resolve(this.values); 19 } 20 21 @Cacheable({ cacheKey: TestClass.setCacheKey }) 22 public getValue(id: number): Promise<number | undefined> { 23 return Promise.resolve(this.values.find((num) => num === id)); 24 } 25 26 // If incrementValue were called with id '1', this.values would be updated and the cached values for 27 // the 'values' key would be cleared and the value at the cache key '1' would be updated to the return 28 // value of this method call 29 @CacheUpdate({ 30 cacheKey: (args, context, returnValue) => args[0], 31 cacheKeysToClear: (args) => ['values'], 32 }) 33 public async incrementValue(id: number): Promise<number> { 34 let newValue = 0; 35 36 this.values = this.values.map((value) => { 37 if (value === id) { 38 newValue = value + 1; 39 return newValue; 40 } 41 42 return value; 43 }); 44 45 return newValue; 46 } 47 48 @Cacheable({ 49 cacheKey: 'users', 50 client: clientAdapter, 51 ttlSeconds: 86400, 52 }) 53 public async getUsers(): Promise<any> { 54 return this.userRepository.findAll(); 55 } 56 57 // If getUserById('123') were called, the return value would be cached 58 // in a hash under user:123, which would expire in 86400 seconds 59 @Cacheable({ 60 cacheKey: TestClass.setCacheKey, 61 hashKey: 'user', 62 client: clientAdapter, 63 ttlSeconds: 86400, 64 }) 65 public async getUserById(id: string): Promise<any> { 66 return this.userRepository.findOne(id); 67 } 68 69 // If getProp('123') were called, the return value would be cached 70 // under 123 in this case for 10 seconds 71 @Cacheable({ cacheKey: TestClass.setCacheKey, ttlSeconds: (args) => args[1] }) 72 public async getProp(id: string, cacheForSeconds: number): Promise<any> { 73 return this.aProp; 74 } 75 76 // If setProp('123', 'newVal') were called, the value cached under 77 // key 123 would be deleted in this case. 78 @CacheClear({ cacheKey: TestClass.setCacheKey }) 79 public async setProp(id: string, value: string): Promise<void> { 80 this.aProp = value; 81 } 82}
@Cacheable
The @Cacheable
decorator first checks for the given key(s) in cache. If a value is available (and not expired), it will be returned. If no value is available, the decorated method will run, and the cache will be set with the return value of that method. It takes CacheOptions
for an argument. The available options are:
1interface CacheOptions { 2 cacheKey?: string | CacheKeyBuilder; // Individual key the result of the decorated method should be stored on 3 hashKey?: string | CacheKeyBuilder; // Set name the result of the decorated method should be stored on (for hashes) 4 client?: CacheClient; // If you would prefer use a different cache client than passed into the adapter, set that here 5 fallbackClient?: CacheClient; // If you would prefer use a different cache client than passed into the adapter as a fallback, set that here 6 noop?: boolean; // Allows for consuming libraries to conditionally disable caching. Set this to true to disable caching for some reason. 7 ttlSeconds?: number | TTLBuilder; // Number of seconds the cached key should live for 8 strategy?: CacheStrategy | CacheStrategyBuilder; // Strategy by which cached values and computed values are handled 9 isCacheable?: IsCacheableBuilder; // Allows for conditional caching based on the arguments passed into the decorated method 10}
@CacheClear
The @CacheClear
decorator first runs the decorated method. If that method does not throw, @CacheClear
will delete the given key(s) in the cache. It takes CacheClearOptions
for an argument. The available options are:
1interface CacheClearOptions { 2 cacheKey?: string | string[] | CacheKeyDeleteBuilder; // Individual key the result of the decorated method should be cleared from 3 hashKey?: string | CacheKeyBuilder; // Set name the result of the decorated method should be stored on (for hashes) 4 client?: CacheClient; // If you would prefer use a different cache client than passed into the adapter, set that here 5 fallbackClient?: CacheClient; // If you would prefer use a different cache client than passed into the adapter as a fallback, set that here 6 noop?: boolean; // Allows for consuming libraries to conditionally disable caching. Set this to true to disable caching for some reason. 7 isPattern?: boolean; // Will remove pattern matched keys from cache (ie: a 'foo' cacheKey will remove ['foolish', 'foo-bar'] matched keys assuming they exist) 8 strategy?: CacheClearStrategy | CacheClearStrategyBuilder; // Strategy by which cached values are cleared 9}
@CacheUpdate
The @CacheUpdate
decorator first runs the decorated method. If that method does not throw, @CacheUpdate
will set the given key(s) in the cache, then clear any keys listed under cacheKeysToClear
. Important note: by default (if cacheKeysToClear
is left undefined), CacheUpdate
will build a cache clear key based on the context of the called method. To avoid this behavior and clear nothing instead, pass null
. It takes CacheUpdateOptions
for an argument. The available options are:
1interface CacheUpdateOptions { 2 cacheKey?: string | CacheKeyBuilder | PostRunKeyBuilder; // Individual key the result of the decorated method should be stored on 3 hashKey?: string | CacheKeyBuilder | PostRunKeyBuilder; // Set name the result of the decorated method should be stored on (for hashes) 4 cacheKeysToClear?: string | string[] | CacheKeyDeleteBuilder | null; // Keys to be cleared from cache after a successful method call. null will prevent deletion. 5 client?: CacheClient; // If you would prefer use a different cache client than passed into the adapter, set that here 6 fallbackClient?: CacheClient; // If you would prefer use a different cache client than passed into the adapter as a fallback, set that here 7 noop?: boolean; // Allows for consuming libraries to conditionally disable caching. Set this to true to disable caching for some reason. 8 isPattern?: boolean; // Will remove pattern matched keys from cache (ie: a 'foo' cacheKey will remove ['foolish', 'foo-bar'] matched keys assuming they exist) 9 strategy?: CacheUpdateStrategy | CacheUpdateStrategyBuilder; // Strategy by which cached values and computed values are handled 10 clearStrategy?: CacheClearStrategy | CacheClearStrategyBuilder; // Strategy by which cached values are cleared 11 clearAndUpdateInParallel?: boolean; // Whether or not to clear and update at the same time (can improve performance, but could create inconsistency) 12}
CacheKeyBuilder
CacheKeyBuilder
can be passed in as the value for cacheKey or hashKey on @Cacheable
, @CacheUpdate
, or @CacheClear
. This is a function that is passed two arguments, args
and context
, where args
is the arguments the decorated method was called with, and context
is the object (this
value) the method was called on. This function must return a string.
For example, if you would like to cache a user, you might want to cache them by id. Refer to the sample above to see how this could be done.
Note
If no cacheKey is passed in, one will be generated by serializing and hashing the method name, arguments, and context in which the method was called. This will not allow you to reliably clear caches, but is available as a convenience.
CacheStrategy
A custom implementation of CacheStrategy
can be passed in to replace the default strategy. The default strategy will always return the cached value, or call the method and cache the result otherwise. If you want to update the cache before its time to live ends, you can implement your own CacheStrategy
like this:
1import { CacheStrategy, CacheStrategyContext } from '@type-cacheable/core'; 2 3export class MyCustomStrategy implements CacheStrategy { 4 async handle(context: CacheStrategyContext): Promise<any> { 5 // Implement your caching logic here 6 } 7}
If you need more details you can check the implementation of the default stratergy here.
Using the CacheManager Directly
It could be the case that you need to read/write data from the cache directly, without decorators. To achieve this you can use cacheManager
. For example:
1import cacheManager from '@type-cacheable/core'; 2import keyGenerator from './utils/cacheKeyGenerator'; 3 4class UserService { 5 private userRepository: Repository<User>; 6 private rolesRepository: Repository<Role>; 7 8 public async blockUser(id: string): Promise<void> { 9 await this.userRepository.update({ id }, { isBlocked: true }); 10 const key = keyGenerator([id], CacheKey.UserRoles); 11 await cacheManager.client?.del(key); 12 } 13 14 public async getUserDetails(id: string): Promise<UserWithRoles> { 15 const key = keyGenerator([id], CacheKey.UserRoles); 16 17 let userRoles = await cacheManager.client?.get(key); 18 if (!userRoles) { 19 userRoles = await this.rolesRepository.find({ userId: id }); 20 await cacheManager.client?.set(key, userRoles, 3600); 21 } 22 23 const user = await this.userRepository.findOne(id); 24 25 return { ...user, userRoles }; 26 } 27}
Disabling Caching
If you want to disable cache operations globally, you can use cacheManager.disable()
. If you want to re-enable caching operations, you may use cacheManager.enable()
.
TypeScript Configuration
1{ 2 "target": "es2015", // at least 3 "experimentalDecorators": true 4}
Contribution
Feel free to contribute by forking this repository, making, testing, and building your changes, then opening a pull request. Please try to maintain a uniform code style.
No vulnerabilities found.
Reason
no dangerous workflow patterns detected
Reason
22 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10
Reason
no binaries found in the repo
Reason
license file detected
Details
- Info: project has a license file: LICENSE:0
- Info: FSF or OSI recognized license: MIT License: LICENSE:0
Reason
6 existing vulnerabilities detected
Details
- Warn: Project is vulnerable to: GHSA-9c47-m6qq-7p4h
- Warn: Project is vulnerable to: GHSA-72xf-g2v4-qvf3
- Warn: Project is vulnerable to: GHSA-j8xg-fqg3-53r7
- Warn: Project is vulnerable to: GHSA-3h5v-q93c-6h6q
- Warn: Project is vulnerable to: GHSA-gxpj-cx7g-858c
- Warn: Project is vulnerable to: GHSA-2p57-rm9w-gvfp
Reason
Found 1/3 approved changesets -- score normalized to 3
Reason
detected GitHub workflow tokens with excessive permissions
Details
- Warn: no topLevel permission defined: .github/workflows/main.yml:1
- Info: no jobLevel write permissions found
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
security policy file not detected
Details
- Warn: no security policy file detected
- Warn: no security file to analyze
- Warn: no security file to analyze
- Warn: no security file to analyze
Reason
project is not fuzzed
Details
- Warn: no fuzzer integrations found
Reason
dependency not pinned by hash detected -- score normalized to 0
Details
- Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/main.yml:26: update your workflow using https://app.stepsecurity.io/secureworkflow/joshuaslate/type-cacheable/main.yml/main?enable=pin
- Warn: containerImage not pinned by hash: Dockerfile:1: pin your Docker image by updating node:lts to node:lts@sha256:ae2f3d4cc65d251352eca01ba668824f651a2ee4d2a37e2efb22649521a483fd
- Info: 0 out of 1 GitHub-owned GitHubAction dependencies pinned
- Info: 0 out of 1 containerImage dependencies pinned
Reason
branch protection not enabled on development/release branches
Details
- Warn: branch protection not enabled for branch 'main'
Reason
SAST tool is not run on all commits -- score normalized to 0
Details
- Warn: 0 commits out of 28 are checked with a SAST tool
Score
4.1
/10
Last Scanned on 2025-01-27
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More