{"id":22399725,"url":"https://github.com/jeengbe/ts-cache","last_synced_at":"2026-02-12T20:03:13.753Z","repository":{"id":224791431,"uuid":"764210170","full_name":"jeengbe/ts-cache","owner":"jeengbe","description":"A strongly typed caching framework that works with any engine.","archived":false,"fork":false,"pushed_at":"2025-03-11T20:31:44.000Z","size":517,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-08-16T21:10:13.084Z","etag":null,"topics":["cache","caching","framework","in-memory","redis"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/@jeengbe/cache","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/jeengbe.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"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}},"created_at":"2024-02-27T17:14:01.000Z","updated_at":"2025-03-11T20:31:48.000Z","dependencies_parsed_at":"2025-07-31T14:38:30.985Z","dependency_job_id":"0ba0c3d2-eec8-41ad-b626-25ee7ed01e14","html_url":"https://github.com/jeengbe/ts-cache","commit_stats":null,"previous_names":["jeengbe/ts-cache"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/jeengbe/ts-cache","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jeengbe%2Fts-cache","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jeengbe%2Fts-cache/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jeengbe%2Fts-cache/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jeengbe%2Fts-cache/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jeengbe","download_url":"https://codeload.github.com/jeengbe/ts-cache/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jeengbe%2Fts-cache/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29379682,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-12T19:05:20.189Z","status":"ssl_error","status_checked_at":"2026-02-12T19:01:44.216Z","response_time":55,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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","caching","framework","in-memory","redis"],"created_at":"2024-12-05T08:09:40.583Z","updated_at":"2026-02-12T20:03:13.746Z","avatar_url":"https://github.com/jeengbe.png","language":"TypeScript","readme":"\u003ch1 align=\"center\"\u003e@jeengbe/cache\u003c/h1\u003e\n\u003cdiv align=\"center\"\u003e\n\nA strongly typed caching framework that works with any engine. In-memory and Redis adapters included.\n\n[![License](https://img.shields.io/npm/l/@jeengbe/cache)](https://github.com/jeengbe/cache/blob/LICENSE.md)\n[![Version](https://img.shields.io/npm/v/@jeengbe/cache)](https://www.npmjs.com/package/@jeengbe/cache)\n![Coverage Badge](https://img.shields.io/badge/Coverage-100%25-brightgreen)\n\n\u003c/div\u003e\n\nIt provides a general `Cache` class that interacts with cache adapters, which are responsible for communicating with the cache engine (e.g. Redis). The package comes with a cache adapter for an in-memory cache that saves its content to the disk, one that does absolutely nothing (no values saved and never returns a value) and a cache adapter for Redis.\n\nTo use several cache instances on the same cache engine, every cache accepts a prefix parameter that is prepended to all keys before they are stored. This allows for different namespaces and versioning stored in the same cache engine.\n\nValues are serialized to string for storage in the cache. By default, `JSON.stringify`/`JSON.parse` are used, but custom serializers may be provided for serializing e.g. with Protocol Buffers.\n\n## Installation\n\nThe package is published as `@jeengbe/cache`. Versions follow Semantic Versioning.\n\n## Gotta go fast, no time to read\n\n```ts\n// service/index.ts\nexport interface MyService {\n  doThing(param: number): Promise\u003cstring\u003e;\n}\n\n// service/cached.ts\ntype MyServiceCacheTypes = {\n  [K in `thing-${number}`]: string;\n};\n\nexport class CachedMyService implements MyService {\n  constructor(\n    private readonly cache: Cache\u003cMyServiceCacheTypes\u003e,\n    private readonly delegate: MyService,\n  ) {}\n\n  async doThing(param: number): Promise\u003cstring\u003e {\n    return await this.cache.cached(`thing-${param}`, () =\u003e\n      this.delegate.doThing(param),\n    );\n  }\n}\n\n// init.ts\nconst cacheAdapter = new RedisCacheAdapter(new Redis(redisConfig));\nconst cache = new Cache\u003cMyServiceCacheTypes\u003e(cacheAdapter);\n```\n\n## Usage\n\n### Create a new cache object\n\nThe generic `Cache` class takes a type parameter that dictates which cache keys correspond to which values.\nFirst, decare the type as an object where the object properties are available cache keys and values the respective values in the cache. Use [template literal types](https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html) to account for patterns like `` `cached-${id}` `` in your keys. Intersect several [mapped types](https://www.typescriptlang.org/docs/handbook/2/mapped-types.html) to merge several objects with template literal types:\n\n```ts\ntype XCacheTypes = {\n  [K in `cached-a-${string}`]: number;\n} \u0026 {\n  [K in `cached-b-${number}`]: string;\n} \u0026 {\n  'general-c': string;\n  'general-d': boolean;\n};\n```\n\nA cache with the above types accepts the following:\n\n- Keys that start with \"cached-a-\" may cache a number.\n- Keys that start with \"cached-b-\" followed by a number may cache a string.\n- The key \"general-c\" may cache a string.\n- The key \"general-d\" may cache a boolean.\n\nAll read/write operations on corresponding instance are strongly typed. To create a new cache object, instantiate the class with a suiting cache adapter and optionally a prefix.\n\n```ts\nimport { Cache, VoidCacheAdapter } from '@jeengbe/cache';\n\nconst xCache = new Cache\u003cXCacheTypes\u003e(new VoidCacheAdapter(), 'x:1');\n```\n\n---\n\nConsider this context for all following code examples in this document:\n\n```ts\ntype Result = { id: string; calculated: number };\n\ndeclare const resultCache: Cache\u003c{\n  [K in `expensive-${string}`]: Result;\n}\u003e;\n```\n\n### Get (`get`/`mget`)\n\nUse `get` to get the value for a single cached key.\n\nTo get the values for several keys in one operation, use `mget`.\n\nIf you want to get the value, and, if none present, calculate and store a new one, consider using `cached`/`mcached` instead.\n\n### Set (`set`/`mset`)\n\nUse `set` to set the cached value for a single key.\n\nTo set the cached values for several keys, use `mset`.\n\nUnlike conventional cache packages, `mset` takes an array of values and a function to determine the cache key for each value.\n\n```ts\ndeclare const items: readonly Result[];\n\nawait resultCache.mset(items, (item) =\u003e `expensive-${item.id}`, '1d');\n```\n\n### Delete (`del`/`mdel`)\n\nUse `del` to delete the cached value for a single key.\n\nTo delete the cached values for several keys, use `mdel`.\n\n### Delete by pattern (`pdel`)\n\nUse `pdel` to delete the cached values for all keys that match the given glob-style pattern.\n\nPlease note that the pattern is not fully typed and can be any string. PRs welcome. :)\n\n### Clear cache (`clear`)\n\nUse `clear` to delete the cached values for all keys.\n\n### Query whether in cache (`has`/`mhas`)\n\nUse `has` to check whether there exists a cached value for a single key.\n\nTo check whether there exist cached values for several keys, use `mhas`.\n\n### Get and if not present, calculate and set (`cached`/`mcached`)\n\nUse `cached` to get the cached value for a cache, and if the value is not in the cache, run a function to produce a value and cache that.\n\nBecause this is a commonly used pattern, this package provides a convenience method for this.\n\nUse `mcached` if you want to get several cached values and only compute those for which there is no value stored in the cache. It takes an array of data, from which each the cache key is generated. If at least one key has no cached value, the producer is called with an array of those data items for whose key no value was cached.\n\nNote that this is no atomic operation and the key is in no way locked while the producer is awaited.\n\n```ts\ndeclare function expensiveFunction(id: string): Promise\u003cResult\u003e;\ndeclare const id: string;\n\nconst result = await resultCache.cached(\n  //  ^? Result\n  `expensive-${id}`,\n  () =\u003e expensiveFunction(id),\n  '1d',\n);\n```\n\n```ts\ndeclare function expensiveBatchFunction(\n  ids: readonly string[],\n): Promise\u003cResult[]\u003e;\ndeclare const ids: string[];\n\nconst results = await resultCache.mcached(\n  //  ^? Result[]\n  ids,\n  (id) =\u003e `expensive-${id}`,\n  (m) =\u003e expensiveBatchFunction(m),\n  '1d',\n);\n```\n\n## Cache Adapters\n\n### Redis\n\n```ts\nimport { RedisCacheAdapter } from '@jeengbe/cache';\nimport { Redis } from 'ioredis';\n\nconst cacheAdapter = new RedisCacheAdapter(new Redis(redisConfig));\n```\n\n### Void\n\nStores no values, get operations always return undefined and has always returns false.\n\n```ts\nimport { VoidCacheAdapter } from '@jeengbe/cache';\n\nconst cacheAdapter = new VoidCacheAdapter();\n```\n\n### In memory\n\nKeeps the values in memory.\n\nThe package `@isaacs/ttlcache` can be used for the cache implementation.\n\n```ts\nimport TtlCache from '@isaacs/ttlcache';\nimport { MemoryCacheAdapter } from '@jeengbe/cache';\n\nconst cacheAdapter = new MemoryCacheAdapter(new TtlCache());\n```\n\nIt is also possible to back up the memory after every write operation. To do that, construct the adapter with a cache backup saver. To save the memory to disk, pass it a `DiskCacheBackupSaver` as shown:\n\n```ts\nimport TtlCache from '@isaacs/ttlcache';\nimport { DiskCacheBackupSaver, MemoryCacheAdapter } from '@jeengbe/cache';\n\nconst cacheAdapter = new MemoryCacheAdapter(\n  new TtlCache(),\n  new DiskCacheBackupSaver(diskCacheBackupLocation),\n);\n```\n\n### No-TTL in memory\n\nKeeps the values in memory, but ignores TTL i.e. values to not expire.\n\nIdeal for unit tests that involve a cache so that you don't have to mock the cache.\n\n```ts\nimport { NoTtlMemoryCacheAdapter } from '@jeengbe/cache';\n\nconst cacheAdapter = new NoTtlMemoryCacheAdapter();\n```\n\n## Notes\n\n### `ms` duration format\n\nAll methods that take a duration (`set`/`cached`, etc.) accept either a ttl in milliseconds, or any duration string that can be parsed by the [`ms`](https://www.npmjs.com/package/ms) package. Since the duration string is strongly typed, you will need an [`as const` assertion](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions) to ensure the returned values are typed properly. All valid durations are exported as the `TtlString` type.\n\n```ts\ndeclare const result: Result;\n\nawait resultCache.set('expensive-1', result, () =\u003e '1d' as const);\n```\n\n### `Array.map` and `mget`, `mdel`, `mhas`\n\nFor the operations `mget`, `mdel`, `mhas`, you may run into compiler errors if you simply map over an input array (see example below). To fix this, add an [`as const` assertion](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions) to ensure that mapped keys are properly typed.\n\n```ts\ndeclare const ids: string[];\n\n// Missing 'as const':\nawait resultCache.mget(\n  ids.map((id) =\u003e `expensive-${id}`),\n  // ~~~~~ Type 'string' is not assignable to type '`expensive-${string}`'.\n);\n\n// Correctly typed:\nawait resultCache.mget(ids.map((id) =\u003e `expensive-${id}` as const));\n```\n\nThe longer explanation is that for a variety of reasons, ``ids.map((id) =\u003e `expensive-${id}`)`` results in a `string[]` instead of exactly `` `expensive-${string}`[] ``. So when `string[]` is used as keys for a strongly typed signature like `mget`, the compiler (rightfully so) complains. By changing it to ``ids.map((id) =\u003e `expensive-${id}` as const)``, we make the type as exact as possible, which then gives the explicit string types we need.\n\nThe reason `as const` is not necessary for `mset`, `mcached` is that the compiler is able to infer the `as const` automatically here. Normally, `` (item) =\u003e `expensive-${item.id}` `` results in `() =\u003e string`, but because the compiler expects an explicit `` () =\u003e `expensive-${string}` `` it also tries to determine a stricter signature for the method, which satisfies the strongly typed signature.\n\nIn theory, this would also work with the above described `map` limitation, but the compiler does not check that deep, so the inferring from `mget` signature -\u003e `map` return type -\u003e `map` callback is not made, and `.map` results in `string[]`.\n\n```ts\ndeclare const items: readonly Result[];\n\nawait resultCache.mset(items, (item) =\u003e `expensive-${item.id}`, '1d');\n```\n\nAlternatively, you can pass a type parameter to `map`, but that's less elegant if you ask me:\n\n```ts\ndeclare const ids: string[];\n\nawait resultCache.mget(\n  ids.map\u003c`expensive-${string}`\u003e((id) =\u003e `expensive-${id}`),\n);\n```\n\n### `safeJsonSerialize`\n\nBy default, the `Cache` class uses `JSON.stringify` to serialize values. However, this method does not validate whether the values are serializable, which can lead to unexpected results. For example, `JSON.stringify(Promise.resolve(1))` returns `\"{}\"`, which is not useful when parsed.\n\nTo handle this issue, the package exports a `safeJsonSerialize` function that ensures only valid, serializable values are processed. You can pass this function to the `Cache` constructor to override the default serialization:\n\n```ts\nimport { Cache, safeJsonSerialize } from '@jeengbe/cache';\n\nnew Cache\u003cMyServiceCacheTypes\u003e(cacheAdapter, undefined, {\n  serialize: safeJsonSerialize,\n});\n```\n\nThe `safeJsonSerialize` function performs runtime checks to validate whether values can be safely serialized. This helps avoid issues like:\n\n- Circular references\n- Non-serializable values like `undefined`, or `Promise`\n- `Date` (`JSON.parse(JSON.stringify(new Date()))` returns a string instead of a `Date` instance)\n\nFor a detailed overview of supported and unsupported values, refer to the unit tests for `safeJsonSerialize`. These tests provide a comprehensive list of scenarios and behaviours.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjeengbe%2Fts-cache","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjeengbe%2Fts-cache","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjeengbe%2Fts-cache/lists"}