{"id":18559581,"url":"https://github.com/de3lo/redis-value-cache","last_synced_at":"2025-04-10T02:30:47.495Z","repository":{"id":256773341,"uuid":"846437674","full_name":"de3lo/redis-value-cache","owner":"de3lo","description":"An in memory cache backed by redis.","archived":false,"fork":false,"pushed_at":"2025-01-31T12:56:18.000Z","size":407,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-19T20:48:37.804Z","etag":null,"topics":["cache","pubsub","redis"],"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/de3lo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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}},"created_at":"2024-08-23T07:54:33.000Z","updated_at":"2025-01-31T12:56:22.000Z","dependencies_parsed_at":"2024-09-13T01:52:41.194Z","dependency_job_id":"635a3216-893b-4a08-9b89-b6753cea5ad3","html_url":"https://github.com/de3lo/redis-value-cache","commit_stats":null,"previous_names":["de3lo/redis-value-cache"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/de3lo%2Fredis-value-cache","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/de3lo%2Fredis-value-cache/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/de3lo%2Fredis-value-cache/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/de3lo%2Fredis-value-cache/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/de3lo","download_url":"https://codeload.github.com/de3lo/redis-value-cache/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248144160,"owners_count":21054877,"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","pubsub","redis"],"created_at":"2024-11-06T21:43:08.576Z","updated_at":"2025-04-10T02:30:47.485Z","avatar_url":"https://github.com/de3lo.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Redis Value Cache\n\nAn in memory cache backed by redis with fallback fetch option. You can cache values from complex objects to strings or numbers.\n\nThis module expects a Redis server where values are stored and messages about changes for these values are published.\n\nThis module only makes read operations on the redis server so you need to save your values and publish updates with other modules.\n\n## Use Case\n\nThis module is designed for applications that require caching of frequently accessed data from a Redis server, with the ability to handle real-time updates.\n\nKey Features:\n\n* Reduced Latency: Frequently accessed data is stored in a local cache, minimizing the need for repeated Redis queries and reducing access times.\n* Real-Time Updates: The module subscribes to Redis channels to receive and apply updates to the cache as soon as they happen.\n* Reduced Server Load: By offloading frequent data access to the local cache, the load on your Redis server is reduced.\n\n## Basics\n\n* The module provides a class RedisValueCache.\n* The class uses an LRU cache to store values.\n* Keys must be the same keys you use in redis.\n* Values can be anything you want except `null` and `undefined`.\n* The constructor requires you to provide functions to deserialize information from redis:\n\t* `genKeyFromMessage`: A function that takes a message from redis and returns the key that needs to be updated.\n\t* `deserialize`: A function that takes a value from redis and returns the value to be cached.\n* Please be sure to handle errors that can occur in these functions appropriately (e.g).\n* If these functions return `null` or `undefined`, the message/value will be ignored.\n* Updates are received over Redis and values can be taken from redis or external sources.\n\n## Options\n\n* `redis`: Options connected to Redis\n\t* `redis.clientOpts` (Optional): Options for the node-redis client ([details](https://github.com/redis/node-redis/blob/master/docs/client-configuration.md)). Used for both `subscriber` and `client`.\n\t* `redis.client` (Optional): Redis client to duplicate for both subscriber and client. Will be prioritized over `redis.clientOpts` if both are provided.\n\t* `redis.channelOpts`: Options whether the `subscriber` should pSubscribe or subscribe normally.\n\t* `redis.getOpts`: Options on how the data should be retrieved from redis. Supported options `GET`, `HGET` and `HGETALL`.\n* `genKeyFromMsg`: Function that takes a message and returns a key that needs to get updated or an object with the key and special RedisGetOpts. The key must be a string.\n* `deserialize`: Function that takes data retrieved from Redis and the key and returns a value that should be saved in the cache.\n* `cacheMaxSize` (Optional): Max number of objects to be stored in the LRU cache (Default = 1000).\n* `errorHandlerStrategy` (Optional): Options for how errors that happen when fetching data from redis should be handled.**Other errors will still be thrown disregarding this option**.\n\t* `\"emit\"`: Emits the error as an `\"unexpectedError\"` event.\n\t* `\"warn\"`: (Default): Uses `console.warn()` to print the error.\n\t* `\"throw\"`: Throws the error.\n\t* `\"ignore\"`: Ignores the error.\n* `fallbackFetchMethod` (Optional): Function that should be used to fetch data if the data was not found in redis.\n* `onMessageStrategy` (Optional): Options for how the RedisValueCache should behave once it receives a message.\n\t* `\"drop\"` (Default): The value will be deleted from the cache.\n\t* `\"refetch\"`: If a value was already the cache the updated value will be fetched.\n\t* `\"fetchAlways\"`: The updated value will always be fetched even if it was not in the cache before.\n* `freeze` (Optional): Whether or not to freeze values when they are cached (Default = true).\n* `cacheFallbackValues` (Optional): Whether or not values returned from the `fallbackFetchMethode` should be cached (Default = false).\n\n## Usage (TypeScript)\n\n### Basic Example\n\n```ts\nimport { RedisValueCache } from \"redis-value-cache\";\n\nconst rvc = new RedisValueCache\u003cstring\u003e({\n\tredis: {\n\t\tchannelOpts: {\n\t\t\ttype: \"subscribe\",\n\t\t\tname: \"channelName\"\n\t\t},\n\t\n\t\tgetOpts: {\n\t\t\ttype: \"GET\"\n\t\t}\n\t},\n\n\tgenKeyFromMsg: (msg: string) =\u003e {\n\t\treturn msg;\n\t},\n\t\n\tdeserialize: (redisValue: string, key: string) =\u003e {\n\t\treturn redisValue;\n\t},\n\n\tonMessageStrategy: \"refetch\",\n\n\terrorHandlerStrategy: \"ignore\"\n})\n\nawait rvc.connect();\n\nconst x = await rvc.get(\"abc\");\n\nawait rvc.quit();\n```\n\n### Basic Example With .new() Function\n\n```ts\nimport { RedisValueCache } from \"redis-value-cache\";\n\nconst rvc = RedisValueCache.new\u003cstring\u003e({\n\tredis: {\n\t\tchannelOpts: {\n\t\t\ttype: \"subscribe\",\n\t\t\tname: \"channelName\"\n\t\t},\n\t\n\t\tgetOpts: {\n\t\t\ttype: \"GET\"\n\t\t}\n\t},\n\n\tgenKeyFromMsg: (msg: string) =\u003e {\n\t\treturn msg;\n\t},\n\t\n\tdeserialize: (redisValue: string, key: string) =\u003e {\n\t\treturn redisValue;\n\t},\n\n\tonMessageStrategy: \"refetch\",\n\n\terrorHandlerStrategy: \"ignore\"\n})\n\n// no need to connect anymore since .new() function returns already connected RedisValueCache\n\nconst x = await rvc.get(\"abc\");\n\nawait rvc.quit();\n```\n\n### More Complex Example\n\n```ts \nimport { RedisValueCache } from \"redis-value-cache\";\nimport { createClient } from \"redis\";\n\ninterface msgType {\n\tid: number;\n\tinfo?: string;\n}\n\n// type of the objects you want to store\ninterface storedObjectType {\n\ttype: \"a\" | \"b\";\n\tdata: Record\u003cstring, unknown\u003e;\n}\n\nconst redisClient = createClient({\n\tsocket: {\n\t\thost: \"localhost\",\n\t\tport: 6379\n\t}\n});\n\nconst opts: Opts\u003cstoredObjectType\u003e = {\n\tredis: {\n\t\tclient: redisClient,\n\t\t// options to subscribe / psubscribe\n\t\tchannelOpts: {\n\t\t\ttype: \"pSubscribe\"\n\t\t\tname: \"channel*\",\n\t\t},\n\t\t// options to define how the client gets the data from redis\n\t\tgetOpts: {\n\t\t\ttype: \"HGET\",\n\t\t\targument: \"info\"\n\t\t}\n\t},\n\n\t// maximum allowed number of objects to be stored in lru-cache\n\tcacheMaxSize: 5000,\n\t// function to extract key from message\n\tgenKeyFromMsg: (msg: string) =\u003e {\n\t\ttry{\n\t\t\tconst msgObject = JSON.parse(msg) as msgType;\n\n\t\t\t// keys must be string and should be same as you use in redis\n\t\t\treturn `key:${msg.id}`;\n\t\t} catch (error) {\n\t\t\t// custom error logic here\n\t\t\t\n\t\t\t// to tell cache to ignore message\n\t\t\treturn null;\n\t\t}\n\t},\n\t// function to adjust redisValue to the way you want to store it\n\t// type of redisValue should change depending on the redisGetOpts\n\tdeserialize: (redisValue: string, key: string) =\u003e {\n\t\ttry{\n\t\t\tconst value = JSON.parse(redisValue) as storedObjectType;\n\t\t\treturn value;\n\t\t} catch (error) {\n\t\t\t// custom error logic here\n\t\t\t\n\t\t\t// to tell cache to ignore message\n\t\t\treturn null;\n\t\t} \n\t},\n\t// how to deal with unexpected errors during fetch of data\n\terrorHandlerStrategy: \"emit\",\n\t// function to get the value from a third source if the `deserialize` function returns null\n\tfallbackFetchMethod: async(key: string) =\u003e {\n\t\tconst resp = await fetch(`url/get/${key}`);\n\t\tconst value = await resp.json() \n\t\treturn value as storedObjectType;\n\t}\n}\n\n// rvc emits all redis client error events as error events\nrvc.on(\"error\", (error, client: \"subscriber\" | \"client\") =\u003e {\n\tconsole.log(error):\n\t// your logic on how to handle error\n});\n\n// is emitted when the rvc is ready to use\nrvc.on(\"ready\", () =\u003e {\n\tconsole.log(\"rvc ready\");\n\t// your logic here\n\t// will be emitted once during connect\n\t// can be useful for reconnection logic\n});\n\n// will emit errors in fetch functions if errorHandle option was set to emit\nrvc.on(\"unexpectedError\", (error) =\u003e {\n\tconsole.log(error);\n\t// your logic here\n})\n\nawait rvc.connect();\n\nconst x = await rvc.get(\"key:1234\",  { clone: true });\n\nawait rvc.quit();\n```\n\n## Emitted Events\n\n| Name                    | When                                                                               | Listener arguments                                         |\n|-------------------------|------------------------------------------------------------------------------------|------------------------------------------------------------|\n| `ready`                 | RedisValueCache is ready to use                                                    | *No arguments*                                             |\n| `error`                 | Either the `client` or the `subscriber` emitted an error event or failed to quit   | `(error: Error, client: \"subscriber\" \\| \"client\")`         |\n| `unexpectedError`       | Something was thrown during a fetch attempt                                        | `(error: unknown, ctx: {key: string} \\| {msg: string} )`   |\n| `dropped`               | A value was dropped because of a message   (`onMessageStrategy` = `\"drop\"`)        | `(key: string)`                                            |\n| `refetched`             | A value was refetched because of a message (`onMessageStrategy` = `\"refetch\"`)     | `(key: string)`                                            |\n| `fetched`               | A value was fetched because of a message   (`onMessageStrategy` = `\"fetchAlways\"`) | `(key: string)`                                            |\n\n\n**!!Warning!!:** You **MUST** listen to `error` events. If a RedisValueCache doesn't have at least one `error` listener registered and an `error` event occurs, that error will be thrown and the Node.js process will exit. See the [`EventEmitter` docs](https://nodejs.org/api/events.html#events_error_events) for more details.\n\nIf an error event is emitted the RedisValueCache **flushes the cache** because it could miss messages.\n\n## Functions\n\n### .new(opts)\n\nStatic function that returns a promise for a new RedisValueCache. The RedisValueCache is ready to use since the `.connect()` function will be already called for you.\n\nParams: \n\n`opts` (Opts): Options for creating a RedisValueCache.\n\nBe aware that this function will throw an error if you have connection issues since no error listener was registered at the time the Clients try to connect to the Redis server.\n\n### .get(\"key\", opts)\n\nFunction to retrieve a value either from cache or Redis. Returns `undefined` if key could not be found.\n\nParams: \n\n* `key` (string): Key of the value you want to look up.\n* `opts` (object | Optional): Options for the get function.\n\t* `opts.clone` (boolean | Optional): Whether or not the value should be cloned before returning it.\n\t* `redisGetOpts` (RedisGetOpts | Optional): To tell the rvc to use a different way to get the value from redis.\n\n#### Why Use The Clone Option?\n\nIf you do not use the clone option, the cache will return a reference to the stored object. This **object is read only** if you did not set the `freeze` option to false.\n\n#### Flowchart\n\n![flowchart not loading](diagrams/getFlow.png)\n\n### .connect()\n\nFunction to call the `.connect()` function for both `subscriber` and `client` and also subscribes/pSubscribes the `subscriber` to the channel(s).\n\nThis function needs to be called in order to be able to use the RedisValueCache if it was not created using the `.new()` function.\n\n### .disconnect()\n\nFunction to call the `.disconnect()` function for both `subscriber` and `client`. This forcibly closes a client's connection to Redis immediately. This also flushes the cache.\n\n### .quit()\n\nFunction to call the `.quit()` function for both  `subscriber` and `client`. This gracefully closes a client's connection to Redis. This also flushes the cache.\n\n### .delete()\n\nFunction to delete a value from the cache. Returns `true` if value was in cache.\n\nParams:\n\n* `key` (string): Key of the value you want to delete.\n\nThis function should not be used to often but can be used to force a refresh for the value.\n\n### getConnected()\n\nFunction returns true if both clients are connected, otherwise returns false.\n\n### .has(\"key\")\n\nFunction that returns `true` if a value for the `key` is in cache and `false` otherwise.\n\nParams: \n\n* `key` (string): Key you want to check.\n\n## Disconnects\n\nIf a disconnect, whether wanted or unwanted, occurs, the cache is flushed since potential change messages could be missed.\n\nBoth clients will try to reconnect to the server depending on the [reconnect Strategy](https://github.com/redis/node-redis/blob/master/docs/client-configuration.md#reconnect-strategy) of the `clientOpts`.\\\nOn a successful reconnect of both clients, a `\"ready\"` event will be emitted.\n\n## Your Functions\n\nFor the functions you pass to the constructor there are the following things to keep in mind.\n\nIf a function returns `null` or `undefined` it will be seen as something went wrong.\\\nFor the `genKeyFromMsg`, this means that nothing more can be done with this message.\\\nFor the `deserialize`, this means that no value was found and the `fallbackFetchMethod` will be used if it was provided.\nFor the `fallbackFetchMethod`, this means that no value was found.\n\nTrowing an error in these functions behaves similarly except if you choose `\"throw\"` for the `errorHandlerStrategy` option.\nAnother option instead of throwing an error is to log/handle the errors in your function and then just return `null` or `undefined`.\n\n## Problems With Freeze\n\nIf you decide to cache values that are JavaScript `Set` or `Map` objects, the objects stored inside the `Set`/`Map` will not be frozen.\nThere might be other types of data structures where this is also the case.\n\nA workaround would be to freeze your objects before putting them in other data structures.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fde3lo%2Fredis-value-cache","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fde3lo%2Fredis-value-cache","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fde3lo%2Fredis-value-cache/lists"}