{"id":13623311,"url":"https://github.com/joshuaslate/type-cacheable","last_synced_at":"2025-05-15T04:03:40.077Z","repository":{"id":32758088,"uuid":"141490233","full_name":"joshuaslate/type-cacheable","owner":"joshuaslate","description":"TypeScript-based caching decorator (currently supports Redis, LRU-Cache and NodeCache)","archived":false,"fork":false,"pushed_at":"2025-05-07T15:46:48.000Z","size":6076,"stargazers_count":185,"open_issues_count":4,"forks_count":37,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-05-07T16:49:50.270Z","etag":null,"topics":["cache","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/joshuaslate.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":["joshuaslate"]}},"created_at":"2018-07-18T21:06:15.000Z","updated_at":"2025-05-07T15:46:49.000Z","dependencies_parsed_at":"2023-12-06T05:26:18.628Z","dependency_job_id":"fe04bea8-1a66-456c-900b-b1129afbc722","html_url":"https://github.com/joshuaslate/type-cacheable","commit_stats":null,"previous_names":[],"tags_count":210,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joshuaslate%2Ftype-cacheable","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joshuaslate%2Ftype-cacheable/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joshuaslate%2Ftype-cacheable/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joshuaslate%2Ftype-cacheable/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/joshuaslate","download_url":"https://codeload.github.com/joshuaslate/type-cacheable/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254270639,"owners_count":22042858,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cache","typescript"],"created_at":"2024-08-01T21:01:30.244Z","updated_at":"2025-05-15T04:03:40.018Z","avatar_url":"https://github.com/joshuaslate.png","language":"TypeScript","funding_links":["https://github.com/sponsors/joshuaslate"],"categories":["TypeScript"],"sub_categories":[],"readme":"# type-cacheable\n\n[![CI](https://github.com/joshuaslate/type-cacheable/actions/workflows/main.yml/badge.svg)](https://github.com/joshuaslate/type-cacheable/actions/workflows/main.yml) [![npm downloads](https://img.shields.io/npm/dm/@type-cacheable/core)](https://www.npmjs.com/package/@type-cacheable/core) [![Code coverage](https://codecov.io/gh/joshuaslate/type-cacheable/branch/main/graph/badge.svg)](https://codecov.io/gh/joshuaslate/type-cacheable)\n\nTypeScript-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.\n\n## Usage\n\n### Installation\n\n```bash\nnpm install --save @type-cacheable/core\n```\n\nor\n\n```bash\nyarn add @type-cacheable/core\n```\n\n### Setup Adapter\n\nYou will need to set up the appropriate adapter for your cache of choice.\n\nRedis:\n\n- `redis` - `@type-cacheable/redis-adapter` - https://github.com/joshuaslate/type-cacheable/tree/main/packages/redis-adapter\n- `ioredis` - `@type-cacheable/ioredis-adapter` - https://github.com/joshuaslate/type-cacheable/tree/main/packages/ioredis-adapter\n\nLRU-Cache\n\n- `lru-cache` - `@type-cacheable/lru-cache-adapter` - https://github.com/joshuaslate/type-cacheable/tree/main/packages/lru-cache-adapter\n\nNode-Cache:\n\n- `node-cache` - `@type-cacheable/node-cache-adapter` https://github.com/joshuaslate/type-cacheable/tree/main/packages/node-cache-adapter\n\nCache-Manager:\n\n- `cache-manager` - `@type-cacheable/cache-manager-adapter` - https://github.com/joshuaslate/type-cacheable/tree/main/packages/cache-manager-adapter\n\n### Change Global Options\n\nSome options can be configured globally for all decorated methods. Here is an example of how you can change these options:\n\n```ts\n// Import and set adapter as above\nimport cacheManager, { CacheManagerOptions } from '@type-cacheable/core';\ncacheManager.setOptions(\u003cCacheManagerOptions\u003e{\n  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.\n  ttlSeconds: 0, // A global setting for the number of seconds the decorated method's results will be cached for.\n});\n```\n\nCurrently, there are three decorators available in this library: `@Cacheable`, `@CacheClear`, and `@CacheUpdate`. Here is a sample of how they can be used:\n\n```ts\nimport Redis from 'redis';\nimport { Cacheable, CacheClear } from '@type-cacheable/core';\n\nconst userClient = Redis.createClient();\nconst clientAdapter = useAdapter(userClient);\n\nclass TestClass {\n  private values: any[] = [1, 2, 3, 4, 5];\n  private userRepository: Repository\u003cUser\u003e;\n\n  // This static method is being called to generate a cache key based on the given arguments.\n  // Not featured here: the second argument, context, which is the instance the method\n  // was called on.\n  static setCacheKey = (args: any[]) =\u003e args[0];\n\n  @Cacheable({ cacheKey: 'values' })\n  public getValues(): Promise\u003cnumber[]\u003e {\n    return Promise.resolve(this.values);\n  }\n\n  @Cacheable({ cacheKey: TestClass.setCacheKey })\n  public getValue(id: number): Promise\u003cnumber | undefined\u003e {\n    return Promise.resolve(this.values.find((num) =\u003e num === id));\n  }\n\n  // If incrementValue were called with id '1', this.values would be updated and the cached values for\n  // the 'values' key would be cleared and the value at the cache key '1' would be updated to the return\n  // value of this method call\n  @CacheUpdate({\n    cacheKey: (args, context, returnValue) =\u003e args[0],\n    cacheKeysToClear: (args) =\u003e ['values'],\n  })\n  public async incrementValue(id: number): Promise\u003cnumber\u003e {\n    let newValue = 0;\n\n    this.values = this.values.map((value) =\u003e {\n      if (value === id) {\n        newValue = value + 1;\n        return newValue;\n      }\n\n      return value;\n    });\n\n    return newValue;\n  }\n\n  @Cacheable({\n    cacheKey: 'users',\n    client: clientAdapter,\n    ttlSeconds: 86400,\n  })\n  public async getUsers(): Promise\u003cany\u003e {\n    return this.userRepository.findAll();\n  }\n\n  // If getUserById('123') were called, the return value would be cached\n  // in a hash under user:123, which would expire in 86400 seconds\n  @Cacheable({\n    cacheKey: TestClass.setCacheKey,\n    hashKey: 'user',\n    client: clientAdapter,\n    ttlSeconds: 86400,\n  })\n  public async getUserById(id: string): Promise\u003cany\u003e {\n    return this.userRepository.findOne(id);\n  }\n\n  // If getProp('123') were called, the return value would be cached\n  // under 123 in this case for 10 seconds\n  @Cacheable({ cacheKey: TestClass.setCacheKey, ttlSeconds: (args) =\u003e args[1] })\n  public async getProp(id: string, cacheForSeconds: number): Promise\u003cany\u003e {\n    return this.aProp;\n  }\n\n  // If setProp('123', 'newVal') were called, the value cached under\n  // key 123 would be deleted in this case.\n  @CacheClear({ cacheKey: TestClass.setCacheKey })\n  public async setProp(id: string, value: string): Promise\u003cvoid\u003e {\n    this.aProp = value;\n  }\n}\n```\n\n#### `@Cacheable`\n\nThe `@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:\n\n```ts\ninterface CacheOptions {\n  cacheKey?: string | CacheKeyBuilder; // Individual key the result of the decorated method should be stored on\n  hashKey?: string | CacheKeyBuilder; // Set name the result of the decorated method should be stored on (for hashes)\n  client?: CacheClient; // If you would prefer use a different cache client than passed into the adapter, set that here\n  fallbackClient?: CacheClient; // If you would prefer use a different cache client than passed into the adapter as a fallback, set that here\n  noop?: boolean; // Allows for consuming libraries to conditionally disable caching. Set this to true to disable caching for some reason.\n  ttlSeconds?: number | TTLBuilder; // Number of seconds the cached key should live for\n  strategy?: CacheStrategy | CacheStrategyBuilder; // Strategy by which cached values and computed values are handled\n  isCacheable?: IsCacheableBuilder; // Allows for conditional caching based on the arguments passed into the decorated method\n}\n```\n\n#### `@CacheClear`\n\nThe `@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:\n\n```ts\ninterface CacheClearOptions {\n  cacheKey?: string | string[] | CacheKeyDeleteBuilder; // Individual key the result of the decorated method should be cleared from\n  hashKey?: string | CacheKeyBuilder; // Set name the result of the decorated method should be stored on (for hashes)\n  client?: CacheClient; // If you would prefer use a different cache client than passed into the adapter, set that here\n  fallbackClient?: CacheClient; // If you would prefer use a different cache client than passed into the adapter as a fallback, set that here\n  noop?: boolean; // Allows for consuming libraries to conditionally disable caching. Set this to true to disable caching for some reason.\n  isPattern?: boolean; // Will remove pattern matched keys from cache (ie: a 'foo' cacheKey will remove ['foolish', 'foo-bar'] matched keys assuming they exist)\n  strategy?: CacheClearStrategy | CacheClearStrategyBuilder; // Strategy by which cached values are cleared\n}\n```\n\n#### `@CacheUpdate`\n\nThe `@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:\n\n```ts\ninterface CacheUpdateOptions {\n  cacheKey?: string | CacheKeyBuilder | PostRunKeyBuilder; // Individual key the result of the decorated method should be stored on\n  hashKey?: string | CacheKeyBuilder | PostRunKeyBuilder; // Set name the result of the decorated method should be stored on (for hashes)\n  cacheKeysToClear?: string | string[] | CacheKeyDeleteBuilder | null; // Keys to be cleared from cache after a successful method call. null will prevent deletion.\n  client?: CacheClient; // If you would prefer use a different cache client than passed into the adapter, set that here\n  fallbackClient?: CacheClient; // If you would prefer use a different cache client than passed into the adapter as a fallback, set that here\n  noop?: boolean; // Allows for consuming libraries to conditionally disable caching. Set this to true to disable caching for some reason.\n  isPattern?: boolean; // Will remove pattern matched keys from cache (ie: a 'foo' cacheKey will remove ['foolish', 'foo-bar'] matched keys assuming they exist)\n  strategy?: CacheUpdateStrategy | CacheUpdateStrategyBuilder; // Strategy by which cached values and computed values are handled\n  clearStrategy?: CacheClearStrategy | CacheClearStrategyBuilder; // Strategy by which cached values are cleared\n  clearAndUpdateInParallel?: boolean; // Whether or not to clear and update at the same time (can improve performance, but could create inconsistency)\n}\n```\n\n##### CacheKeyBuilder\n\n`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.\n\nFor 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.\n\n##### Note\n\nIf 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.\n\n##### CacheStrategy\n\nA 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:\n\n```ts\nimport { CacheStrategy, CacheStrategyContext } from '@type-cacheable/core';\n\nexport class MyCustomStrategy implements CacheStrategy {\n  async handle(context: CacheStrategyContext): Promise\u003cany\u003e {\n    // Implement your caching logic here\n  }\n}\n```\n\nIf you need more details you can check the implementation of the default stratergy [here](./packages/core/lib/strategies/DefaultStrategy.ts).\n\n### Using the CacheManager Directly\n\nIt 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:\n\n```ts\nimport cacheManager from '@type-cacheable/core';\nimport keyGenerator from './utils/cacheKeyGenerator';\n\nclass UserService {\n  private userRepository: Repository\u003cUser\u003e;\n  private rolesRepository: Repository\u003cRole\u003e;\n\n  public async blockUser(id: string): Promise\u003cvoid\u003e {\n    await this.userRepository.update({ id }, { isBlocked: true });\n    const key = keyGenerator([id], CacheKey.UserRoles);\n    await cacheManager.client?.del(key);\n  }\n\n  public async getUserDetails(id: string): Promise\u003cUserWithRoles\u003e {\n    const key = keyGenerator([id], CacheKey.UserRoles);\n\n    let userRoles = await cacheManager.client?.get(key);\n    if (!userRoles) {\n      userRoles = await this.rolesRepository.find({ userId: id });\n      await cacheManager.client?.set(key, userRoles, 3600);\n    }\n\n    const user = await this.userRepository.findOne(id);\n\n    return { ...user, userRoles };\n  }\n}\n```\n\n### Disabling Caching\nIf 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()`.\n\n### TypeScript Configuration\n\n```ts\n{\n  \"target\": \"es2015\", // at least\n  \"experimentalDecorators\": true\n}\n```\n\n## Contribution\n\nFeel 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.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoshuaslate%2Ftype-cacheable","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjoshuaslate%2Ftype-cacheable","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoshuaslate%2Ftype-cacheable/lists"}