diff --git a/README.md b/README.md index 1789a51d37..e6332764e4 100644 --- a/README.md +++ b/README.md @@ -293,6 +293,7 @@ The Node Redis client class is an Nodejs EventEmitter and it emits an event each | `error` | An error has occurred—usually a network issue such as "Socket closed unexpectedly" | `(error: Error)` | | `reconnecting` | Client is trying to reconnect to the server | _No arguments_ | | `sharded-channel-moved` | See [here](https://github.com/redis/node-redis/blob/master/docs/pub-sub.md#sharded-channel-moved-event) | See [here](https://github.com/redis/node-redis/blob/master/docs/pub-sub.md#sharded-channel-moved-event) | +| `invalidate` | Client Tracking is on with `emitInvalidate` and a key is invalidated | `(key: RedisItem \| null)` | > :warning: You **MUST** listen to `error` events. If a client doesn't have at least one `error` listener registered and > an `error` 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. diff --git a/packages/client/lib/client/index.ts b/packages/client/lib/client/index.ts index cf5763357a..fada269302 100644 --- a/packages/client/lib/client/index.ts +++ b/packages/client/lib/client/index.ts @@ -145,6 +145,11 @@ export interface RedisClientOptions< * Tag to append to library name that is sent to the Redis server */ clientInfoTag?: string; + /** + * When set to true, client tracking is turned on and the client emits `invalidate` events when it receives invalidation messages from the redis server. + * Mutually exclusive with `clientSideCache` option. + */ + emitInvalidate?: boolean; /** * Controls how the client handles Redis Enterprise maintenance push notifications. * @@ -525,6 +530,19 @@ export default class RedisClient< this.#clientSideCache?.invalidate(null) } + return true + }); + } else if (options?.emitInvalidate) { + this.#queue.addPushHandler((push: Array): boolean => { + if (push[0].toString() !== 'invalidate') return false; + + if (push[1] !== null) { + for (const key of push[1]) { + this.emit('invalidate', key); + } + } else { + this.emit('invalidate', null); + } return true }); } @@ -534,11 +552,15 @@ export default class RedisClient< if (options?.clientSideCache && options?.RESP !== 3) { throw new Error('Client Side Caching is only supported with RESP3'); } - + if (options?.emitInvalidate && options?.RESP !== 3) { + throw new Error('emitInvalidate is only supported with RESP3'); + } + if (options?.clientSideCache && options?.emitInvalidate) { + throw new Error('emitInvalidate is not supported (or necessary) when clientSideCache is enabled'); + } if (options?.maintPushNotifications && options?.maintPushNotifications !== 'disabled' && options?.RESP !== 3) { throw new Error('Graceful Maintenance is only supported with RESP3'); } - } #initiateOptions(options?: RedisClientOptions): RedisClientOptions | undefined { @@ -743,11 +765,15 @@ export default class RedisClient< } }); } - + if (this.#clientSideCache) { commands.push({cmd: this.#clientSideCache.trackingOn()}); } + if (this.#options?.emitInvalidate) { + commands.push({cmd: ['CLIENT', 'TRACKING', 'ON']}); + } + const { tls, host } = this.#options!.socket as RedisTcpSocketOptions; const maintenanceHandshakeCmd = await EnterpriseMaintenanceManager.getHandshakeCommand(!!tls, host!, this.#options!); if(maintenanceHandshakeCmd) {