From f34b11c1b7a2e3f5ff54975eb4ddd336b5f518b7 Mon Sep 17 00:00:00 2001 From: LiaoLiao Date: Sun, 1 Jan 2023 21:30:39 +0800 Subject: [PATCH] docs: update (#419) --- README.md | 2 +- docs/v2/README.md | 823 +++++++++++++++++++++++++++++++++++ docs/v2/dependency-graph.svg | 433 ++++++++++++++++++ docs/v3/cluster.md | 286 ++++++++++++ docs/v3/dependency-graph.svg | 469 ++++++++++++++++++++ docs/v3/examples.md | 139 ++++++ docs/v3/health-checks.md | 104 +++++ docs/v3/redis.md | 362 +++++++++++++++ docs/v4/cluster.md | 322 ++++++++++++++ docs/v4/dependency-graph.svg | 469 ++++++++++++++++++++ docs/v4/examples.md | 151 +++++++ docs/v4/health-checks.md | 104 +++++ docs/v4/redis.md | 440 +++++++++++++++++++ packages/redis/README.md | 2 +- 14 files changed, 4104 insertions(+), 2 deletions(-) create mode 100644 docs/v2/README.md create mode 100644 docs/v2/dependency-graph.svg create mode 100644 docs/v3/cluster.md create mode 100644 docs/v3/dependency-graph.svg create mode 100644 docs/v3/examples.md create mode 100644 docs/v3/health-checks.md create mode 100644 docs/v3/redis.md create mode 100644 docs/v4/cluster.md create mode 100644 docs/v4/dependency-graph.svg create mode 100644 docs/v4/examples.md create mode 100644 docs/v4/health-checks.md create mode 100644 docs/v4/redis.md diff --git a/README.md b/README.md index 5aee00fb..1420309c 100644 --- a/README.md +++ b/README.md @@ -259,5 +259,5 @@ Distributed under the MIT License. See `LICENSE` for more information. [license-shield]: https://img.shields.io/npm/l/@liaoliaots/nestjs-redis?style=for-the-badge [license-url]: https://github.com/liaoliaots/nestjs-redis/blob/main/LICENSE [vulnerabilities-shield]: https://img.shields.io/snyk/vulnerabilities/npm/@liaoliaots/nestjs-redis?style=for-the-badge -[workflow-shield]: https://img.shields.io/github/workflow/status/liaoliaots/nestjs-redis/testing?label=TESTING&style=for-the-badge +[workflow-shield]: https://img.shields.io/github/actions/workflow/status/liaoliaots/nestjs-redis/testing.yaml?label=TESTING&style=for-the-badge [workflow-url]: https://github.com/liaoliaots/nestjs-redis/actions/workflows/testing.yaml diff --git a/docs/v2/README.md b/docs/v2/README.md new file mode 100644 index 00000000..b2f42b1a --- /dev/null +++ b/docs/v2/README.md @@ -0,0 +1,823 @@ +# Welcome to nestjs-redis 👋 + +[![npm (tag)](https://img.shields.io/npm/v/@liaoliaots/nestjs-redis/latest?style=flat-square)](https://www.npmjs.com/package/@liaoliaots/nestjs-redis) +[![npm (scoped with tag)](https://img.shields.io/npm/v/@liaoliaots/nestjs-redis/next?style=flat-square)](https://www.npmjs.com/package/@liaoliaots/nestjs-redis/v/3.0.0-next.2) +![npm](https://img.shields.io/npm/dw/@liaoliaots/nestjs-redis?style=flat-square) +[![GitHub](https://img.shields.io/github/license/liaoliaots/nestjs-redis?style=flat-square)](https://github.com/liaoliaots/nestjs-redis/blob/main/LICENSE) +[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier) +[![CodeFactor](https://www.codefactor.io/repository/github/liaoliaots/nestjs-redis/badge)](https://www.codefactor.io/repository/github/liaoliaots/nestjs-redis) +[![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://github.com/liaoliaots/nestjs-redis/graphs/commit-activity) +[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg)](https://conventionalcommits.org) + +> Redis([ioredis](https://github.com/luin/ioredis)) module for NestJS framework + +## Features + +- Supports **redis** and **cluster** +- Supports health check +- Supports specify single or multiple clients +- Supports inject a client directly or get a client via namespace + +## Documentation + +- [Test coverage](#test-coverage) +- [Install](#install) +- [Redis](#redis) + - [Usage](#redis-usage) + - [Health check](#redis-health-check) + - [Options](#redis-options) +- [Cluster](#cluster) + - [Usage](#cluster-usage) + - [Health check](#cluster-health-check) + - [Options](#cluster-options) +- [Examples](#examples) + - [Redis](#examples-redis) + - [default](#examples-default) + - [sentinel](#examples-sentinel) + - [Cluster](#examples-cluster) +- [Package dependency overview](#package-dependency-overview) +- [Todo](#todo) + +## Test coverage + +| Statements | Branches | Functions | Lines | +| ------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | ------------------------------------------------------------------------ | --------------------------------------------------------------------- | +| ![Statements](https://img.shields.io/badge/statements-92.47%25-brightgreen.svg) | ![Branches](https://img.shields.io/badge/branches-88.64%25-yellow.svg) | ![Functions](https://img.shields.io/badge/functions-84.85%25-yellow.svg) | ![Lines](https://img.shields.io/badge/lines-92.67%25-brightgreen.svg) | + +## Install + +### NestJS 8: + +```sh +$ npm install --save @liaoliaots/nestjs-redis@next ioredis @nestjs/terminus@next +$ npm install --save-dev @types/ioredis +``` + +```sh +$ yarn add @liaoliaots/nestjs-redis@next ioredis @nestjs/terminus@next +$ yarn add --dev @types/ioredis +``` + +### NestJS 7: + +```sh +$ npm install --save @liaoliaots/nestjs-redis@2 ioredis @nestjs/terminus@7 +$ npm install --save-dev @types/ioredis +``` + +```sh +$ yarn add @liaoliaots/nestjs-redis@2 ioredis @nestjs/terminus@7 +$ yarn add --dev @types/ioredis +``` + +## Redis + +

Usage

+ +**First**, register the RedisModule in app.module.ts: + +The RedisModule is a [global module](https://docs.nestjs.com/modules#global-modules). Once defined, the module is available everywhere. + +```TypeScript +import { Module } from '@nestjs/common'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [RedisModule.forRoot()] +}) +export class AppModule {} +``` + +with async config: + +```TypeScript +import { Module } from '@nestjs/common'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; +import { ConfigService, ConfigModule } from '@nestjs/config'; + +@Module({ + imports: [ + RedisModule.forRootAsync({ + useFactory: (configService: ConfigService) => ({ config: configService.get('redis') }), + inject: [ConfigService], + imports: [ConfigModule] + + // useClass: + + // useExisting: + }) + ] +}) +export class AppModule {} +``` + +with single client: + +```TypeScript +import { Module } from '@nestjs/common'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [ + RedisModule.forRoot({ + config: { + host: 'localhost', + port: 6380, + + // or with URL + // url: 'redis://:your_password@localhost:6380/0' + } + }) + ] +}) +export class AppModule {} +``` + +with multiple clients: + +**NOTE:** If you don't set the namespace for a client, its namespace is set to default. Please note that you shouldn't have multiple client without a namespace, or with the same namespace, otherwise they will get overridden. + +```TypeScript +import { Module } from '@nestjs/common'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [ + RedisModule.forRoot({ + closeClient: true, + config: [ + { + host: 'localhost', + port: 6380, + db: 0, + enableAutoPipelining: true + }, + { + namespace: 'cache', + host: 'localhost', + port: 6380, + db: 1, + enableAutoPipelining: true + } + ] + }) + ] +}) +export class AppModule {} +``` + +**In some cases**, you can move the same config to **defaultOptions**. + +**NOTE:** The **defaultOptions** only work with multiple clients. + +```TypeScript +import { Module } from '@nestjs/common'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [ + RedisModule.forRoot({ + closeClient: true, + defaultOptions: { + host: 'localhost', + port: 6380, + enableAutoPipelining: true + }, + config: [ + { + db: 0 + }, + { + namespace: 'cache', + db: 1 + } + ] + }) + ] +}) +export class AppModule {} +``` + +You can also override the **defaultOptions**: + +```TypeScript +import { Module } from '@nestjs/common'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [ + RedisModule.forRoot({ + closeClient: true, + defaultOptions: { + host: 'localhost', + port: 6380, + enableAutoPipelining: true + }, + config: [ + { + db: 0 + }, + { + namespace: 'cache', + db: 1, + enableAutoPipelining: false // override the default options + } + ] + }) + ] +}) +export class AppModule {} +``` + +with **onClientCreated**: + +```TypeScript +import { Module } from '@nestjs/common'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [ + RedisModule.forRoot({ + config: { + onClientCreated(client) { + client.on('ready', () => {}); + client.on('error', err => {}); + }, + host: 'localhost', + port: 6380 + } + }) + ] +}) +export class AppModule {} +``` + +**Next**, use redis clients: + +via decorator: + +```TypeScript +import { Injectable } from '@nestjs/common'; +import { Redis } from 'ioredis'; +import { InjectRedis, DEFAULT_REDIS_CLIENT } from '@liaoliaots/nestjs-redis'; + +@Injectable() +export class AppService { + constructor( + @InjectRedis() private readonly clientDefault: Redis, + // or + // @InjectRedis(DEFAULT_REDIS_CLIENT) private readonly clientDefault: Redis, + + @InjectRedis('cache') private readonly clientCache: Redis + ) {} + + async set(): Promise { + await this.clientDefault.set('foo', 'bar'); + + await this.clientCache.set('foo', 'bar'); + } +} +``` + +via service: + +```TypeScript +import { Injectable } from '@nestjs/common'; +import { Redis } from 'ioredis'; +import { DEFAULT_REDIS_CLIENT, RedisService } from '@liaoliaots/nestjs-redis'; + +@Injectable() +export class AppService { + private clientDefault: Redis; + + private clientCache: Redis; + + private clients; + + constructor(private readonly redisService: RedisService) { + this.clientDefault = this.redisService.getClient(); + // or + // this.clientDefault = this.redisService.getClient(DEFAULT_REDIS_CLIENT); + + this.clientCache = this.redisService.getClient('cache'); + + this.clients = this.redisService.clients; // get all clients + } + + async set(): Promise { + await this.clientDefault.set('foo', 'bar'); + + await this.clientCache.set('foo', 'bar'); + } +} +``` + +

Health check

+ +**First**, register the [TerminusModule](https://docs.nestjs.com/recipes/terminus) in app.module.ts: + +```TypeScript +import { Module } from '@nestjs/common'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; +import { TerminusModule } from '@nestjs/terminus'; + +@Module({ + imports: [RedisModule.forRoot({ config: { host: 'localhost', port: 6380 } }), TerminusModule] +}) +export class AppModule {} +``` + +**Next**, use health check: + +```TypeScript +import { Controller, Get } from '@nestjs/common'; +import { HealthCheckService, HealthCheckResult } from '@nestjs/terminus'; +import { RedisHealthIndicator, DEFAULT_REDIS_CLIENT } from '@liaoliaots/nestjs-redis'; + +@Controller('app') +export class AppController { + constructor(private readonly health: HealthCheckService, private readonly redisIndicator: RedisHealthIndicator) {} + + @Get() + healthCheck(): Promise { + return this.health.check([ + () => this.redisIndicator.isHealthy('clientDefault', { namespace: DEFAULT_REDIS_CLIENT }) + ]); + } +} +``` + +And then send a GET request to **/app**, if redis is in a healthy state, you will get: + +```TypeScript +{ + status: 'ok', + info: { + clientDefault: { + status: 'up' + } + }, + error: {}, + details: { + clientDefault: { + status: 'up' + } + } +} +``` + +

Options

+ +#### RedisModuleOptions + +| Name | Type | Default value | Description | +| ----------------------------------------------------------------------------------------------- | -------------------------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| closeClient | boolean | false | If `true`, all clients will be closed automatically on nestjs application shutdown. To use **closeClient**, you must enable listeners by calling **enableShutdownHooks()**. [See details.](https://docs.nestjs.com/fundamentals/lifecycle-events#application-shutdown) | +| [defaultOptions](https://github.com/luin/ioredis/blob/master/API.md#new-redisport-host-options) | object | undefined | The default options for every client. | +| config | ClientOptions or ClientOptions[] | {} | Specify single or multiple clients. | + +#### ClientOptions + +| Name | Type | Default value | Description | +| ---------------------------------------------------------------------------------------------------- | ---------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| namespace | string or symbol | Symbol('default') | The name of the client, and must be unique. You can import **DEFAULT_REDIS_CLIENT** to reference the default value. | +| url | string | undefined | The URL([redis://](https://www.iana.org/assignments/uri-schemes/prov/redis) or [rediss://](https://www.iana.org/assignments/uri-schemes/prov/rediss)) specifies connection options. | +| onClientCreated | function | undefined | Once the client has been created, this function will be executed immediately. | +| **...**[RedisOptions](https://github.com/luin/ioredis/blob/master/API.md#new-redisport-host-options) | object | - | Extends the [RedisOptions](https://github.com/luin/ioredis/blob/master/lib/redis/RedisOptions.ts#L8). | + +#### RedisHealthCheckOptions + +| Name | Type | Default value | Description | +| --------- | ---------------- | ------------- | --------------------------------------------------------------------- | +| namespace | string or symbol | - | The namespace of redis client, this client will execute health check. | + +## Cluster + +

Usage

+ +**First**, register the ClusterModule in app.module.ts: + +The ClusterModule is a [global module](https://docs.nestjs.com/modules#global-modules). Once defined, the module is available everywhere. + +```TypeScript +import { Module } from '@nestjs/common'; +import { ClusterModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [ + ClusterModule.forRoot({ + config: { + nodes: [{ host: 'localhost', port: 16380 }] + } + }) + ] +}) +export class AppModule {} +``` + +with async config: + +```TypeScript +import { Module } from '@nestjs/common'; +import { ClusterModule } from '@liaoliaots/nestjs-redis'; +import { ConfigService, ConfigModule } from '@nestjs/config'; + +@Module({ + imports: [ + ClusterModule.forRootAsync({ + useFactory: (configService: ConfigService) => ({ config: configService.get('cluster') }), + inject: [ConfigService], + imports: [ConfigModule] + + // useClass: + + // useExisting: + }) + ] +}) +export class AppModule {} +``` + +with single client: + +```TypeScript +import { Module } from '@nestjs/common'; +import { ClusterModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [ + ClusterModule.forRoot({ + config: { + nodes: [{ host: 'localhost', port: 16380 }], + options: { redisOptions: { password: 'your_password' } } + } + }) + ] +}) +export class AppModule {} +``` + +with multiple clients: + +**NOTE:** If you don't set the namespace for a client, its namespace is set to default. Please note that you shouldn't have multiple client without a namespace, or with the same namespace, otherwise they will get overridden. + +```TypeScript +import { Module } from '@nestjs/common'; +import { ClusterModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [ + ClusterModule.forRoot({ + closeClient: true, + config: [ + { + nodes: [{ host: 'localhost', port: 16380 }], + options: { redisOptions: { password: 'your_password' } } + }, + { + namespace: 'cache', + nodes: [{ host: 'localhost', port: 16383 }], + options: { redisOptions: { password: 'your_password' } } + } + ] + }) + ] +}) +export class AppModule {} +``` + +with **onClientCreated**: + +```TypeScript +import { Module } from '@nestjs/common'; +import { ClusterModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [ + ClusterModule.forRoot({ + config: { + onClientCreated(client) { + client.on('ready', () => {}); + client.on('error', err => {}); + }, + nodes: [{ host: 'localhost', port: 16380 }], + options: { redisOptions: { password: 'your_password' } } + } + }) + ] +}) +export class AppModule {} +``` + +**Next**, use cluster clients: + +via decorator: + +```TypeScript +import { Injectable } from '@nestjs/common'; +import { Cluster } from 'ioredis'; +import { InjectCluster, DEFAULT_CLUSTER_CLIENT } from '@liaoliaots/nestjs-redis'; + +@Injectable() +export class AppService { + constructor( + @InjectCluster() private readonly clientDefault: Cluster, + // or + // @InjectCluster(DEFAULT_CLUSTER_CLIENT) private readonly clientDefault: Redis, + + @InjectCluster('cache') private readonly clientCache: Cluster + ) {} + + async set(): Promise { + await this.clientDefault.set('foo', 'bar'); + + await this.clientCache.set('foo', 'bar'); + } +} +``` + +via service: + +```TypeScript +import { Injectable } from '@nestjs/common'; +import { Cluster } from 'ioredis'; +import { DEFAULT_CLUSTER_CLIENT, ClusterService } from '@liaoliaots/nestjs-redis'; + +@Injectable() +export class AppService { + private clientDefault: Cluster; + + private clientCache: Cluster; + + private clients; + + constructor(private readonly clusterService: ClusterService) { + this.clientDefault = this.clusterService.getClient(); + // or + // this.clientDefault = this.clusterService.getClient(DEFAULT_CLUSTER_CLIENT); + + this.clientCache = this.clusterService.getClient('cache'); + + this.clients = this.clusterService.clients; // get all clients + } + + async set(): Promise { + await this.clientDefault.set('foo', 'bar'); + + await this.clientCache.set('foo', 'bar'); + } +} + +``` + +

Health check

+ +**First**, register the [TerminusModule](https://docs.nestjs.com/recipes/terminus) in app.module.ts: + +```TypeScript +import { Module } from '@nestjs/common'; +import { ClusterModule } from '@liaoliaots/nestjs-redis'; +import { TerminusModule } from '@nestjs/terminus'; + +@Module({ + imports: [ + ClusterModule.forRoot({ + config: { + nodes: [{ host: 'localhost', port: 16380 }], + options: { redisOptions: { password: 'your_password' } } + } + }), + TerminusModule + ] +}) +export class AppModule {} +``` + +**Next**, use health check: + +```TypeScript +import { Controller, Get } from '@nestjs/common'; +import { HealthCheckService, HealthCheckResult } from '@nestjs/terminus'; +import { ClusterHealthIndicator, DEFAULT_CLUSTER_CLIENT } from '@liaoliaots/nestjs-redis'; + +@Controller('app') +export class AppController { + constructor( + private readonly health: HealthCheckService, + private readonly clusterIndicator: ClusterHealthIndicator + ) {} + + @Get() + healthCheck(): Promise { + return this.health.check([ + () => this.clusterIndicator.isHealthy('clientDefault', { namespace: DEFAULT_CLUSTER_CLIENT }) + ]); + } +} + +``` + +And then send a GET request to **/app**, if redis is in a healthy state, you will get: + +```TypeScript +{ + status: 'ok', + info: { + clientDefault: { + status: 'up' + } + }, + error: {}, + details: { + clientDefault: { + status: 'up' + } + } +} +``` + +

Options

+ +#### ClusterModuleOptions + +| Name | Type | Default value | Description | +| ----------- | -------------------------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| closeClient | boolean | false | If `true`, all clients will be closed automatically on nestjs application shutdown. To use **closeClient**, you must enable listeners by calling **enableShutdownHooks()**. [See details.](https://docs.nestjs.com/fundamentals/lifecycle-events#application-shutdown) | +| config | ClientOptions or ClientOptions[] | {} | Specify single or multiple clients. | + +#### ClientOptions + +| Name | Type | Default value | Description | +| --------------------------------------------------------------------------------------------- | -------------------------------- | ----------------- | --------------------------------------------------------------------------------------------------------------------- | +| namespace | string or symbol | Symbol('default') | The name of the client, and must be unique. You can import **DEFAULT_CLUSTER_CLIENT** to reference the default value. | +| [nodes](https://github.com/luin/ioredis/blob/master/API.md#new-clusterstartupnodes-options) | { port: number; host: string }[] | - | A list of nodes of the cluster. | +| [options](https://github.com/luin/ioredis/blob/master/API.md#new-clusterstartupnodes-options) | object | undefined | The [cluster options](https://github.com/luin/ioredis/blob/master/lib/cluster/ClusterOptions.ts#L30). | +| onClientCreated | function | undefined | Once the client has been created, this function will be executed immediately. | + +#### ClusterHealthCheckOptions + +| Name | Type | Default value | Description | +| --------- | ---------------- | ------------- | ----------------------------------------------------------------------- | +| namespace | string or symbol | - | The namespace of cluster client, this client will execute health check. | + +## Examples + +

Redis

+ +- If your redis server has no password, the host is localhost, and the port is 6379: + +```TypeScript +import { Module } from '@nestjs/common'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [RedisModule.forRoot()] +}) +export class AppModule {} +``` + +or + +```TypeScript +import { Module } from '@nestjs/common'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [RedisModule.forRoot({ config: {} })] +}) +export class AppModule {} +``` + +or + +```TypeScript +import { Module } from '@nestjs/common'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [RedisModule.forRoot({ config: { namespace: 'cache' } })] +}) +export class AppModule {} +``` + +- If redis sentinel config is: + +| name | ip | port | password | +| ------------------------ | --------- | ---- | -------- | +| master | localhost | 6380 | 123456 | +| slave1 | localhost | 6381 | 123456 | +| slave2 | localhost | 6382 | 123456 | +| sentinel1 (**mymaster**) | localhost | 7380 | 654321 | +| sentinel2 (**mymaster**) | localhost | 7381 | 654321 | + +```TypeScript +import { Module } from '@nestjs/common'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [ + RedisModule.forRoot({ + defaultOptions: { + sentinels: [ + { + host: 'localhost', + port: 7380 + }, + { + host: 'localhost', + port: 7381 + } + ], + sentinelPassword: '654321', + password: '123456' + }, + config: [ + // get master node from the sentinel group + { name: 'mymaster', role: 'master' }, + // get a random slave node from the sentinel group, read-only by default + { namespace: 'random slave', name: 'mymaster', role: 'slave' } + ] + }) + ] +}) +export class AppModule {} +``` + +

Cluster

+ +- If cluster config is: + +cluster 1: + +| name | ip | port | password | +| ------- | --------- | ----- | -------- | +| master1 | localhost | 16380 | 123456 | +| master2 | localhost | 16381 | 123456 | +| master3 | localhost | 16382 | 123456 | + +cluster 2: + +| name | ip | port | password | +| ------- | --------- | ----- | -------- | +| master1 | localhost | 16383 | 654321 | +| master2 | localhost | 16384 | 654321 | +| master3 | localhost | 16385 | 654321 | + +```TypeScript +import { Module } from '@nestjs/common'; +import { ClusterModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [ + ClusterModule.forRoot({ + config: [ + { + nodes: [{ host: 'localhost', port: 16380 }], + options: { redisOptions: { password: '123456' } } + }, + { + namespace: 'cache', + nodes: [{ host: 'localhost', port: 16383 }], + options: { redisOptions: { password: '654321' } } + } + ] + }) + ] +}) +export class AppModule {} +``` + +## Package dependency overview + +![](./dependency-graph.svg) + +## Todo + +- Load `@nestjs/terminus` lazily + +## Author + +👤 **LiaoLiao** + +- Website: https://github.com/liaoliaots +- Github: [@liaoliaots](https://github.com/liaoliaots) + +## 🤝 Contributing + +Contributions, issues and feature requests are welcome! + +Feel free to check [issues page](https://github.com/liaoliaots/nestjs-redis/issues). + +## Show your support + +Give a ⭐️ if this project helped you! + +## 📝 License + +Copyright © 2021 [LiaoLiao](https://github.com/liaoliaots). + +This project is [MIT](https://github.com/liaoliaots/nestjs-redis/blob/main/LICENSE) licensed. + +--- + +_This README was generated with ❤️ by [readme-md-generator](https://github.com/kefranabg/readme-md-generator)_ diff --git a/docs/v2/dependency-graph.svg b/docs/v2/dependency-graph.svg new file mode 100644 index 00000000..28fd73e6 --- /dev/null +++ b/docs/v2/dependency-graph.svg @@ -0,0 +1,433 @@ + + + + + + +G + + + +cluster/cluster-core.module.js + +cluster/cluster-core.module.js + + + +cluster/cluster.constants.js + +cluster/cluster.constants.js + + + +cluster/cluster-core.module.js->cluster/cluster.constants.js + + + + + +cluster/cluster.health.js + +cluster/cluster.health.js + + + +cluster/cluster-core.module.js->cluster/cluster.health.js + + + + + +cluster/cluster.providers.js + +cluster/cluster.providers.js + + + +cluster/cluster-core.module.js->cluster/cluster.providers.js + + + + + +cluster/cluster.service.js + +cluster/cluster.service.js + + + +cluster/cluster-core.module.js->cluster/cluster.service.js + + + + + +cluster/common/index.js + +cluster/common/index.js + + + +cluster/cluster-core.module.js->cluster/common/index.js + + + + + +cluster/cluster.health.js->cluster/cluster.service.js + + + + + +errors/index.js + +errors/index.js + + + +cluster/cluster.health.js->errors/index.js + + + + + +cluster/cluster.providers.js->cluster/cluster.constants.js + + + + + +cluster/cluster.providers.js->cluster/cluster.service.js + + + + + +cluster/cluster.providers.js->cluster/common/index.js + + + + + +cluster/cluster.providers.js->errors/index.js + + + + + +cluster/cluster.service.js->cluster/cluster.constants.js + + + + + +cluster/cluster.service.js->errors/index.js + + + + + +utils/index.js + +utils/index.js + + + +cluster/cluster.service.js->utils/index.js + + + + + +cluster/common/cluster-utils.js + +cluster/common/cluster-utils.js + + + +cluster/common/index.js->cluster/common/cluster-utils.js + + + + + +cluster/common/cluster.decorator.js + +cluster/common/cluster.decorator.js + + + +cluster/common/index.js->cluster/common/cluster.decorator.js + + + + + +errors/messages.constant.js + +errors/messages.constant.js + + + +errors/index.js->errors/messages.constant.js + + + + + +errors/redis.error.js + +errors/redis.error.js + + + +errors/index.js->errors/redis.error.js + + + + + +cluster/cluster.module.js + +cluster/cluster.module.js + + + +cluster/cluster.module.js->cluster/cluster-core.module.js + + + + + +cluster/common/cluster.decorator.js->cluster/cluster.constants.js + + + + + +index.js + +index.js + + + +index.js->cluster/cluster.constants.js + + + + + +index.js->cluster/cluster.health.js + + + + + +index.js->cluster/cluster.service.js + + + + + +index.js->cluster/common/index.js + + + + + +index.js->cluster/cluster.module.js + + + + + +redis/common/index.js + +redis/common/index.js + + + +index.js->redis/common/index.js + + + + + +redis/redis.constants.js + +redis/redis.constants.js + + + +index.js->redis/redis.constants.js + + + + + +redis/redis.health.js + +redis/redis.health.js + + + +index.js->redis/redis.health.js + + + + + +redis/redis.module.js + +redis/redis.module.js + + + +index.js->redis/redis.module.js + + + + + +redis/redis.service.js + +redis/redis.service.js + + + +index.js->redis/redis.service.js + + + + + +redis/common/redis-utils.js + +redis/common/redis-utils.js + + + +redis/common/index.js->redis/common/redis-utils.js + + + + + +redis/common/redis.decorator.js + +redis/common/redis.decorator.js + + + +redis/common/index.js->redis/common/redis.decorator.js + + + + + +redis/redis.health.js->redis/redis.service.js + + + + + +redis/redis-core.module.js + +redis/redis-core.module.js + + + +redis/redis.module.js->redis/redis-core.module.js + + + + + +redis/redis.service.js->errors/index.js + + + + + +redis/redis.service.js->utils/index.js + + + + + +redis/redis.service.js->redis/redis.constants.js + + + + + +redis/common/redis.decorator.js->redis/redis.constants.js + + + + + +redis/redis-core.module.js->redis/common/index.js + + + + + +redis/redis-core.module.js->redis/redis.constants.js + + + + + +redis/redis-core.module.js->redis/redis.health.js + + + + + +redis/redis-core.module.js->redis/redis.service.js + + + + + +redis/redis.providers.js + +redis/redis.providers.js + + + +redis/redis-core.module.js->redis/redis.providers.js + + + + + +redis/redis.providers.js->errors/index.js + + + + + +redis/redis.providers.js->redis/common/index.js + + + + + +redis/redis.providers.js->redis/redis.constants.js + + + + + +redis/redis.providers.js->redis/redis.service.js + + + + + diff --git a/docs/v3/cluster.md b/docs/v3/cluster.md new file mode 100644 index 00000000..417bb742 --- /dev/null +++ b/docs/v3/cluster.md @@ -0,0 +1,286 @@ +# Cluster + +## Usage + +**Firstly**, we need to import the `ClusterModule` into our root module: + +```TypeScript +import { Module } from '@nestjs/common'; +import { ClusterModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [ + ClusterModule.forRoot({ + closeClient: true, + config: { + nodes: [{ host: '127.0.0.1', port: 16380 }], + options: { redisOptions: { password: 'clusterpassword1' } } + } + }) + ] +}) +export class AppModule {} +``` + +> HINT: The `ClusterModule` is a [global module](https://docs.nestjs.com/modules#global-modules). Once defined, this module is available everywhere. + +**Now** we can use cluster in two ways. + +via decorator: + +```TypeScript +import { Injectable } from '@nestjs/common'; +import { InjectCluster, DEFAULT_CLUSTER_NAMESPACE } from '@liaoliaots/nestjs-redis'; +import { Cluster } from 'ioredis'; + +@Injectable() +export class AppService { + constructor( + @InjectCluster() private readonly defaultClusterClient: Cluster + // or + // @InjectCluster(DEFAULT_CLUSTER_NAMESPACE) private readonly defaultClusterClient: Cluster + ) {} + + async ping(): Promise { + return await this.defaultClusterClient.ping(); + } +} +``` + +via service: + +```TypeScript +import { Injectable } from '@nestjs/common'; +import { ClusterService, DEFAULT_CLUSTER_NAMESPACE } from '@liaoliaots/nestjs-redis'; +import { Cluster } from 'ioredis'; + +@Injectable() +export class AppService { + private readonly defaultClusterClient: Cluster; + + constructor(private readonly clusterService: ClusterService) { + this.defaultClusterClient = this.clusterService.getClient(); + // or + // this.defaultClusterClient = this.clusterService.getClient(DEFAULT_CLUSTER_NAMESPACE); + } + + async ping(): Promise { + return await this.defaultClusterClient.ping(); + } +} +``` + +## Configuration + +### ClusterModuleOptions + +| Name | Type | Default value | Description | +| ----------- | ------------------------------------ | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| closeClient | boolean | false | If `true`, all clients will be closed automatically on nestjs application shutdown. To use `closeClient`, you **must enable listeners** by calling `app.enableShutdownHooks()`. [Read more about the application shutdown.](https://docs.nestjs.com/fundamentals/lifecycle-events#application-shutdown) | +| readyLog | boolean | false | If `true`, will show a message when the client is ready. | +| config | `ClientOptions` or `ClientOptions`[] | {} | Specify single or multiple clients. | + +### ClientOptions + +| Name | Type | Default value | Description | +| --------------------------------------------------------------------------------------------- | -------------------------------------------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| namespace | string or symbol | Symbol('default') | The name of the client, and must be unique. You can import `DEFAULT_CLUSTER_NAMESPACE` to reference the default value. | +| [nodes](https://github.com/luin/ioredis/blob/master/API.md#new-clusterstartupnodes-options) | `{ host?: string; port?: number }[]` or `string[]` | - | A list of nodes of the cluster. The **first** argument of `new Cluster(startupNodes, options).` | +| [options](https://github.com/luin/ioredis/blob/master/API.md#new-clusterstartupnodes-options) | object | undefined | The [cluster options](https://github.com/luin/ioredis/blob/master/lib/cluster/ClusterOptions.ts#L30). The **second** argument of `new Cluster(startupNodes, options).` | +| onClientCreated | function | undefined | This function will be executed as soon as the client is created. | + +### Asynchronous configuration + +via `useFactory`: + +```TypeScript +import { Module } from '@nestjs/common'; +import { ClusterModule, ClusterModuleOptions } from '@liaoliaots/nestjs-redis'; +import { ConfigService, ConfigModule } from '@nestjs/config'; + +@Module({ + imports: [ + ClusterModule.forRootAsync({ + imports: [ConfigModule], + inject: [ConfigService], + useFactory: async (configService: ConfigService): Promise => { + await somePromise(); + + return { + closeClient: true, + config: { + nodes: [{ host: '127.0.0.1', port: 16380 }], + options: { redisOptions: { password: 'clusterpassword1' } } + } + }; + } + }) + ] +}) +export class AppModule {} +``` + +via `useClass`: + +```TypeScript +import { Module, Injectable } from '@nestjs/common'; +import { ClusterModule, ClusterOptionsFactory, ClusterModuleOptions } from '@liaoliaots/nestjs-redis'; + +@Injectable() +export class ClusterConfigService implements ClusterOptionsFactory { + async createClusterOptions(): Promise { + await somePromise(); + + return { + closeClient: true, + config: { + nodes: [{ host: '127.0.0.1', port: 16380 }], + options: { redisOptions: { password: 'clusterpassword1' } } + } + }; + } +} + +@Module({ + imports: [ + ClusterModule.forRootAsync({ + useClass: ClusterConfigService + }) + ] +}) +export class AppModule {} +``` + +... or via `useExisting`, if you wish to use an existing configuration provider imported from a different module. + +```TypeScript +ClusterModule.forRootAsync({ + imports: [ConfigModule], + useExisting: ConfigService +}) +``` + +### readyLog + +```TypeScript +import { Module } from '@nestjs/common'; +import { ClusterModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [ + ClusterModule.forRoot({ + readyLog: true, + config: { + namespace: 'default', + nodes: [{ host: '127.0.0.1', port: 16380 }], + options: { redisOptions: { password: 'clusterpassword1' } } + } + }) + ] +}) +export class AppModule {} +``` + +The `ClusterModule` will display a message when `CLUSTER INFO` reporting the cluster is able to receive commands. + +```sh +[Nest] 18886 - 09/16/2021, 6:19:56 PM LOG [ClusterModule] default: Connected successfully to the server +``` + +### Single client + +```TypeScript +import { Module } from '@nestjs/common'; +import { ClusterModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [ + ClusterModule.forRoot({ + config: { + nodes: [{ host: '127.0.0.1', port: 16380 }], + options: { redisOptions: { password: 'clusterpassword1' } } + + // or with URL + // nodes: ['redis://:clusterpassword1@127.0.0.1:16380'] + } + }) + ] +}) +export class AppModule {} +``` + +### Multiple clients + +```TypeScript +import { Module } from '@nestjs/common'; +import { ClusterModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [ + ClusterModule.forRoot({ + config: [ + { + nodes: [{ host: '127.0.0.1', port: 16380 }], + options: { redisOptions: { password: 'clusterpassword1' } } + }, + { + namespace: 'cluster2', + nodes: [{ host: '127.0.0.1', port: 16480 }], + options: { redisOptions: { password: 'clusterpassword2' } } + } + ] + }) + ] +}) +export class AppModule {} +``` + +with URL: + +```TypeScript +import { Module } from '@nestjs/common'; +import { ClusterModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [ + ClusterModule.forRoot({ + config: [ + { + nodes: ['redis://:clusterpassword1@127.0.0.1:16380'] + }, + { + namespace: 'cluster2', + nodes: ['redis://:clusterpassword2@127.0.0.1:16480'] + } + ] + }) + ] +}) +export class AppModule {} +``` + +> HINT: If you don't set the namespace for a client, its namespace is set to default. Please note that you shouldn't have multiple client without a namespace, or with the same namespace, otherwise they will get overridden. + +### onClientCreated + +For example, we can listen to the error event of the cluster client. + +```TypeScript +import { Module } from '@nestjs/common'; +import { ClusterModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [ + ClusterModule.forRoot({ + config: { + nodes: [{ host: '127.0.0.1', port: 16380 }], + options: { redisOptions: { password: 'clusterpassword1' } }, + onClientCreated(client) { + client.on('error', err => {}); + } + } + }) + ] +}) +export class AppModule {} +``` diff --git a/docs/v3/dependency-graph.svg b/docs/v3/dependency-graph.svg new file mode 100644 index 00000000..61e304e7 --- /dev/null +++ b/docs/v3/dependency-graph.svg @@ -0,0 +1,469 @@ + + + + + + +G + + + +dist/cluster/cluster-core.module.js + +dist/cluster/cluster-core.module.js + + + +dist/cluster/cluster.constants.js + +dist/cluster/cluster.constants.js + + + +dist/cluster/cluster-core.module.js->dist/cluster/cluster.constants.js + + + + + +dist/cluster/cluster.providers.js + +dist/cluster/cluster.providers.js + + + +dist/cluster/cluster-core.module.js->dist/cluster/cluster.providers.js + + + + + +dist/cluster/cluster.service.js + +dist/cluster/cluster.service.js + + + +dist/cluster/cluster-core.module.js->dist/cluster/cluster.service.js + + + + + +dist/cluster/common/index.js + +dist/cluster/common/index.js + + + +dist/cluster/cluster-core.module.js->dist/cluster/common/index.js + + + + + +dist/errors/index.js + +dist/errors/index.js + + + +dist/cluster/cluster-core.module.js->dist/errors/index.js + + + + + +dist/cluster/cluster.providers.js->dist/cluster/cluster.constants.js + + + + + +dist/cluster/cluster.providers.js->dist/cluster/cluster.service.js + + + + + +dist/cluster/cluster.providers.js->dist/cluster/common/index.js + + + + + +dist/cluster/cluster.service.js->dist/cluster/cluster.constants.js + + + + + +dist/cluster/cluster.service.js->dist/errors/index.js + + + + + +dist/utils/index.js + +dist/utils/index.js + + + +dist/cluster/cluster.service.js->dist/utils/index.js + + + + + +dist/cluster/common/cluster.decorator.js + +dist/cluster/common/cluster.decorator.js + + + +dist/cluster/common/index.js->dist/cluster/common/cluster.decorator.js + + + + + +dist/cluster/common/cluster.utils.js + +dist/cluster/common/cluster.utils.js + + + +dist/cluster/common/index.js->dist/cluster/common/cluster.utils.js + + + + + +dist/errors/messages.constant.js + +dist/errors/messages.constant.js + + + +dist/errors/index.js->dist/errors/messages.constant.js + + + + + +dist/errors/redis.error.js + +dist/errors/redis.error.js + + + +dist/errors/index.js->dist/errors/redis.error.js + + + + + +dist/cluster/cluster.module.js + +dist/cluster/cluster.module.js + + + +dist/cluster/cluster.module.js->dist/cluster/cluster-core.module.js + + + + + +dist/cluster/common/cluster.decorator.js->dist/cluster/cluster.constants.js + + + + + +dist/cluster/common/cluster.decorator.js->dist/utils/index.js + + + + + +dist/cluster/common/cluster.utils.js->dist/cluster/cluster.constants.js + + + + + +dist/cluster/common/cluster.utils.js->dist/utils/index.js + + + + + +dist/health/index.js + +dist/health/index.js + + + +dist/health/indicators/redis.health.js + +dist/health/indicators/redis.health.js + + + +dist/health/index.js->dist/health/indicators/redis.health.js + + + + + +dist/health/redis-health.module.js + +dist/health/redis-health.module.js + + + +dist/health/index.js->dist/health/redis-health.module.js + + + + + +dist/health/indicators/redis.health.js->dist/errors/index.js + + + + + +dist/health/redis-health.module.js->dist/health/indicators/redis.health.js + + + + + +dist/index.js + +dist/index.js + + + +dist/index.js->dist/cluster/cluster.constants.js + + + + + +dist/index.js->dist/cluster/cluster.service.js + + + + + +dist/index.js->dist/cluster/common/index.js + + + + + +dist/index.js->dist/cluster/cluster.module.js + + + + + +dist/redis/common/index.js + +dist/redis/common/index.js + + + +dist/index.js->dist/redis/common/index.js + + + + + +dist/redis/redis.constants.js + +dist/redis/redis.constants.js + + + +dist/index.js->dist/redis/redis.constants.js + + + + + +dist/redis/redis.module.js + +dist/redis/redis.module.js + + + +dist/index.js->dist/redis/redis.module.js + + + + + +dist/redis/redis.service.js + +dist/redis/redis.service.js + + + +dist/index.js->dist/redis/redis.service.js + + + + + +dist/redis/common/redis.decorator.js + +dist/redis/common/redis.decorator.js + + + +dist/redis/common/index.js->dist/redis/common/redis.decorator.js + + + + + +dist/redis/common/redis.utils.js + +dist/redis/common/redis.utils.js + + + +dist/redis/common/index.js->dist/redis/common/redis.utils.js + + + + + +dist/redis/redis-core.module.js + +dist/redis/redis-core.module.js + + + +dist/redis/redis.module.js->dist/redis/redis-core.module.js + + + + + +dist/redis/redis.service.js->dist/errors/index.js + + + + + +dist/redis/redis.service.js->dist/utils/index.js + + + + + +dist/redis/redis.service.js->dist/redis/redis.constants.js + + + + + +dist/redis/common/redis.decorator.js->dist/utils/index.js + + + + + +dist/redis/common/redis.decorator.js->dist/redis/redis.constants.js + + + + + +dist/redis/common/redis.utils.js->dist/utils/index.js + + + + + +dist/redis/common/redis.utils.js->dist/redis/redis.constants.js + + + + + +dist/redis/redis-core.module.js->dist/errors/index.js + + + + + +dist/redis/redis-core.module.js->dist/redis/common/index.js + + + + + +dist/redis/redis-core.module.js->dist/redis/redis.constants.js + + + + + +dist/redis/redis-core.module.js->dist/redis/redis.service.js + + + + + +dist/redis/redis.providers.js + +dist/redis/redis.providers.js + + + +dist/redis/redis-core.module.js->dist/redis/redis.providers.js + + + + + +dist/redis/redis.providers.js->dist/redis/common/index.js + + + + + +dist/redis/redis.providers.js->dist/redis/redis.constants.js + + + + + +dist/redis/redis.providers.js->dist/redis/redis.service.js + + + + + +health/index.js + +health/index.js + + + +health/index.js->dist/health/index.js + + + + + diff --git a/docs/v3/examples.md b/docs/v3/examples.md new file mode 100644 index 00000000..49795b3a --- /dev/null +++ b/docs/v3/examples.md @@ -0,0 +1,139 @@ +# Examples + +## Redis + +### Default + +If the redis server does **not** have a password, the host is **127.0.0.1** and the port is **6379**: + +```TypeScript +import { Module } from '@nestjs/common'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [RedisModule.forRoot()] +}) +export class AppModule {} +``` + +... or + +```TypeScript +import { Module } from '@nestjs/common'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [RedisModule.forRoot({ closeClient: true })] +}) +export class AppModule {} +``` + +... or + +```TypeScript +import { Module } from '@nestjs/common'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [RedisModule.forRoot({ closeClient: true, config: { namespace: 'default' } })] +}) +export class AppModule {} +``` + +### Sentinel + +| name | ip | port | password | +| ------------------------ | --------- | ---- | ---------------- | +| master | 127.0.0.1 | 6380 | masterpassword1 | +| slave1 | 127.0.0.1 | 6480 | masterpassword1 | +| slave2 | 127.0.0.1 | 6481 | masterpassword1 | +| sentinel1 (**mymaster**) | 127.0.0.1 | 7380 | sentinelpassword | +| sentinel2 (**mymaster**) | 127.0.0.1 | 7381 | sentinelpassword | + +> HINT: When using Sentinel in Master-Slave setup, if you want to set the passwords for Master and Slave nodes, consider having the same password for them ([#7292](https://github.com/redis/redis/issues/7292)). + +```TypeScript +import { Module } from '@nestjs/common'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [ + RedisModule.forRoot({ + closeClient: true, + commonOptions: { + sentinels: [ + { + host: '127.0.0.1', + port: 7380 + }, + { + host: '127.0.0.1', + port: 7381 + } + ], + sentinelPassword: 'sentinelpassword', + password: 'masterpassword1' + }, + config: [ + // create a master from the sentinel group + { namespace: 'master node', name: 'mymaster', role: 'master' }, + // create a random slave from the sentinel group + { namespace: 'slave node', name: 'mymaster', role: 'slave' } + ] + }) + ] +}) +export class AppModule {} +``` + +> HINT: The `commonOptions` option works only with multiple clients. + +> INFO: Read more about sentinel [here](https://github.com/luin/ioredis#sentinel). + +## Cluster + +### Multiple Clients + +cluster 1: + +| name | ip | port | password | +| ------- | --------- | ----- | ---------------- | +| master1 | 127.0.0.1 | 16380 | clusterpassword1 | +| master2 | 127.0.0.1 | 16381 | clusterpassword1 | +| master3 | 127.0.0.1 | 16382 | clusterpassword1 | + +cluster 2: + +| name | ip | port | password | +| ------- | --------- | ----- | ---------------- | +| master1 | 127.0.0.1 | 16480 | clusterpassword2 | +| master2 | 127.0.0.1 | 16481 | clusterpassword2 | +| master3 | 127.0.0.1 | 16482 | clusterpassword2 | + +```TypeScript +import { Module } from '@nestjs/common'; +import { ClusterModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [ + ClusterModule.forRoot({ + closeClient: true, + config: [ + { + namespace: 'cluster1', + nodes: [{ host: '127.0.0.1', port: 16380 }], + options: { redisOptions: { password: 'clusterpassword1' } } + }, + { + namespace: 'cluster2', + nodes: [{ host: '127.0.0.1', port: 16480 }], + options: { redisOptions: { password: 'clusterpassword2' } } + } + ] + }) + ] +}) +export class AppModule {} +``` + +> INFO: Read more about cluster [here](https://github.com/luin/ioredis#cluster). diff --git a/docs/v3/health-checks.md b/docs/v3/health-checks.md new file mode 100644 index 00000000..6112ec9f --- /dev/null +++ b/docs/v3/health-checks.md @@ -0,0 +1,104 @@ +# Health Checks + +## Usage + +**Firstly**, we need to install the required package: + +```sh +$ npm install --save @nestjs/terminus +``` + +**Secondly**, we need to import the `TerminusModule` and `RedisHealthModule` into our business module: + +```TypeScript +import { Module } from '@nestjs/common'; +import { ClusterModule, RedisModule } from '@liaoliaots/nestjs-redis'; +import { RedisHealthModule } from '@liaoliaots/nestjs-redis/health'; // note the import path +import { TerminusModule } from '@nestjs/terminus'; +import { AppController } from './app.controller'; + +// Suppose we want to check health for redis and cluster, so we need to import the `ClusterModule` and `RedisModule`. + +@Module({ + imports: [ + ClusterModule.forRoot({ + closeClient: true, + config: { + nodes: [{ host: '127.0.0.1', port: 16380 }], + options: { redisOptions: { password: 'clusterpassword1' } } + } + }), + RedisModule.forRoot({ + closeClient: true, + config: { + host: '127.0.0.1', + port: 6380, + password: 'masterpassword1' + } + }), + TerminusModule, + RedisHealthModule + ], + controllers: [AppController] +}) +export class AppModule {} +``` + +> HINT: Both `TerminusModule` and `RedisHealthModule` aren't global modules. + +**Now** let's create a health check: + +```TypeScript +import { Controller, Get } from '@nestjs/common'; +import { HealthCheckService, HealthCheck, HealthCheckResult } from '@nestjs/terminus'; +import { InjectRedis, InjectCluster } from '@liaoliaots/nestjs-redis'; +import { RedisHealthIndicator } from '@liaoliaots/nestjs-redis/health'; // note the import path +import { Redis, Cluster } from 'ioredis'; + +@Controller() +export class AppController { + constructor( + private readonly health: HealthCheckService, + private readonly redis: RedisHealthIndicator, + @InjectRedis() private readonly defaultRedisClient: Redis, + @InjectCluster() private readonly defaultClusterClient: Cluster + ) {} + + @Get('health') + @HealthCheck() + async healthChecks(): Promise { + return await this.health.check([ + () => this.redis.checkHealth('default-redis-client', { client: this.defaultRedisClient }), + () => this.redis.checkHealth('default-cluster-client', { client: this.defaultClusterClient }) + ]); + } +} + +``` + +If your redis and cluster are reachable, you should now see the following JSON-result when requesting http://localhost:3000/health with a GET request: + +```json +{ + "status": "ok", + "info": { + "default-redis-client": { + "status": "up" + }, + "default-cluster-client": { + "status": "up" + } + }, + "error": {}, + "details": { + "default-redis-client": { + "status": "up" + }, + "default-cluster-client": { + "status": "up" + } + } +} +``` + +> INFO: Read more about `@nestjs/terminus` [here](https://docs.nestjs.com/recipes/terminus). diff --git a/docs/v3/redis.md b/docs/v3/redis.md new file mode 100644 index 00000000..2cbfd7f0 --- /dev/null +++ b/docs/v3/redis.md @@ -0,0 +1,362 @@ +# Redis + +## Usage + +**Firstly**, we need to import the `RedisModule` into our root module: + +```TypeScript +import { Module } from '@nestjs/common'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [ + RedisModule.forRoot({ + closeClient: true, + config: { + host: '127.0.0.1', + port: 6380, + password: 'masterpassword1' + } + }) + ] +}) +export class AppModule {} +``` + +> HINT: The `RedisModule` is a [global module](https://docs.nestjs.com/modules#global-modules). Once defined, this module is available everywhere. + +**Now** we can use redis in two ways. + +via decorator: + +```TypeScript +import { Injectable } from '@nestjs/common'; +import { InjectRedis, DEFAULT_REDIS_NAMESPACE } from '@liaoliaots/nestjs-redis'; +import { Redis } from 'ioredis'; + +@Injectable() +export class AppService { + constructor( + @InjectRedis() private readonly defaultRedisClient: Redis + // or + // @InjectRedis(DEFAULT_REDIS_NAMESPACE) private readonly defaultRedisClient: Redis + ) {} + + async ping(): Promise { + return await this.defaultRedisClient.ping(); + } +} +``` + +via service: + +```TypeScript +import { Injectable } from '@nestjs/common'; +import { RedisService, DEFAULT_REDIS_NAMESPACE } from '@liaoliaots/nestjs-redis'; +import { Redis } from 'ioredis'; + +@Injectable() +export class AppService { + private readonly defaultRedisClient: Redis; + + constructor(private readonly redisService: RedisService) { + this.defaultRedisClient = this.redisService.getClient(); + // or + // this.defaultRedisClient = this.redisService.getClient(DEFAULT_REDIS_NAMESPACE); + } + + async ping(): Promise { + return await this.defaultRedisClient.ping(); + } +} +``` + +## Configuration + +### RedisModuleOptions + +| Name | Type | Default value | Description | +| ------------- | ------------------------------------ | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| closeClient | boolean | false | If `true`, all clients will be closed automatically on nestjs application shutdown. To use `closeClient`, you **must enable listeners** by calling `app.enableShutdownHooks()`. [Read more about the application shutdown.](https://docs.nestjs.com/fundamentals/lifecycle-events#application-shutdown) | +| commonOptions | object | undefined | The common options for each client. | +| readyLog | boolean | false | If `true`, will show a message when the client is ready. | +| config | `ClientOptions` or `ClientOptions`[] | {} | Specify single or multiple clients. | + +### ClientOptions + +| Name | Type | Default value | Description | +| ---------------------------------------------------------------------------------------------------- | ---------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| namespace | string or symbol | Symbol('default') | The name of the client, and must be unique. You can import `DEFAULT_REDIS_NAMESPACE` to reference the default value. | +| url | string | undefined | The URL([redis://](https://www.iana.org/assignments/uri-schemes/prov/redis) or [rediss://](https://www.iana.org/assignments/uri-schemes/prov/rediss)) specifies connection options. | +| onClientCreated | function | undefined | This function will be executed as soon as the client is created. | +| **...**[RedisOptions](https://github.com/luin/ioredis/blob/master/API.md#new-redisport-host-options) | object | - | Extends the [RedisOptions](https://github.com/luin/ioredis/blob/master/lib/redis/RedisOptions.ts#L8). | + +### Asynchronous configuration + +via `useFactory`: + +```TypeScript +import { Module } from '@nestjs/common'; +import { RedisModule, RedisModuleOptions } from '@liaoliaots/nestjs-redis'; +import { ConfigService, ConfigModule } from '@nestjs/config'; + +@Module({ + imports: [ + RedisModule.forRootAsync({ + imports: [ConfigModule], + inject: [ConfigService], + useFactory: async (configService: ConfigService): Promise => { + await somePromise(); + + return { + closeClient: true, + config: { + host: '127.0.0.1', + port: 6380, + password: 'masterpassword1' + } + }; + } + }) + ] +}) +export class AppModule {} +``` + +via `useClass`: + +```TypeScript +import { Module, Injectable } from '@nestjs/common'; +import { RedisModule, RedisOptionsFactory, RedisModuleOptions } from '@liaoliaots/nestjs-redis'; + +@Injectable() +export class RedisConfigService implements RedisOptionsFactory { + async createRedisOptions(): Promise { + await somePromise(); + + return { + closeClient: true, + config: { + host: '127.0.0.1', + port: 6380, + password: 'masterpassword1' + } + }; + } +} + +@Module({ + imports: [ + RedisModule.forRootAsync({ + useClass: RedisConfigService + }) + ] +}) +export class AppModule {} +``` + +... or via `useExisting`, if you wish to use an existing configuration provider imported from a different module. + +```TypeScript +RedisModule.forRootAsync({ + imports: [ConfigModule], + useExisting: ConfigService +}) +``` + +### readyLog + +```TypeScript +import { Module } from '@nestjs/common'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [ + RedisModule.forRoot({ + readyLog: true, + config: { + namespace: 'default', + host: '127.0.0.1', + port: 6380, + password: 'masterpassword1' + } + }) + ] +}) +export class AppModule {} +``` + +The `RedisModule` will display a message when the server reports that it is ready to receive commands. + +```sh +[Nest] 17581 - 09/16/2021, 6:03:35 PM LOG [RedisModule] default: Connected successfully to the server +``` + +### Single client + +```TypeScript +import { Module } from '@nestjs/common'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [ + RedisModule.forRoot({ + config: { + host: '127.0.0.1', + port: 6380, + password: 'masterpassword1' + + // or with URL + // url: 'redis://:masterpassword1@127.0.0.1:6380/0' + } + }) + ] +}) +export class AppModule {} +``` + +### Multiple clients + +```TypeScript +import { Module } from '@nestjs/common'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [ + RedisModule.forRoot({ + config: [ + { + host: '127.0.0.1', + port: 6380, + password: 'masterpassword1' + }, + { + namespace: 'master2', + host: '127.0.0.1', + port: 6381, + password: 'masterpassword2' + } + ] + }) + ] +}) +export class AppModule {} +``` + +with URL: + +```TypeScript +import { Module } from '@nestjs/common'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [ + RedisModule.forRoot({ + config: [ + { + url: 'redis://:masterpassword1@127.0.0.1:6380/0' + }, + { + namespace: 'master2', + url: 'redis://:masterpassword2@127.0.0.1:6381/0' + } + ] + }) + ] +}) +export class AppModule {} +``` + +> HINT: If you don't set the namespace for a client, its namespace is set to default. Please note that you shouldn't have multiple client without a namespace, or with the same namespace, otherwise they will get overridden. + +### commonOptions + +**In some cases**, you can move the same config of multiple clients to `commonOptions`. + +> HINT: The `commonOptions` option works only with multiple clients. + +```TypeScript +import { Module } from '@nestjs/common'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [ + RedisModule.forRoot({ + commonOptions: { + enableAutoPipelining: true + }, + config: [ + { + host: '127.0.0.1', + port: 6380, + password: 'masterpassword1' + }, + { + namespace: 'master2', + host: '127.0.0.1', + port: 6381, + password: 'masterpassword2' + } + ] + }) + ] +}) +export class AppModule {} +``` + +You can also override the `commonOptions`: + +```TypeScript +import { Module } from '@nestjs/common'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [ + RedisModule.forRoot({ + commonOptions: { + enableAutoPipelining: true + }, + config: [ + { + host: '127.0.0.1', + port: 6380, + password: 'masterpassword1' + }, + { + namespace: 'master2', + host: '127.0.0.1', + port: 6381, + password: 'masterpassword2', + enableAutoPipelining: false + } + ] + }) + ] +}) +export class AppModule {} +``` + +### onClientCreated + +For example, we can listen to the error event of the redis client. + +```TypeScript +import { Module } from '@nestjs/common'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [ + RedisModule.forRoot({ + config: { + host: '127.0.0.1', + port: 6380, + password: 'masterpassword1', + onClientCreated(client) { + client.on('error', err => {}); + } + } + }) + ] +}) +export class AppModule {} +``` diff --git a/docs/v4/cluster.md b/docs/v4/cluster.md new file mode 100644 index 00000000..1a6b78e6 --- /dev/null +++ b/docs/v4/cluster.md @@ -0,0 +1,322 @@ +# Cluster + +## Usage + +**Firstly**, we need to import the `ClusterModule` into our root module: + +```TypeScript +import { Module } from '@nestjs/common'; +import { ClusterModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [ + ClusterModule.forRoot({ + closeClient: true, + config: { + nodes: [{ host: '127.0.0.1', port: 16380 }], + options: { redisOptions: { password: 'clusterpassword1' } } + } + }) + ] +}) +export class AppModule {} +``` + +> HINT: The `ClusterModule` is a [global module](https://docs.nestjs.com/modules#global-modules). Once defined, this module is available everywhere. + +**Now** we can use cluster in two ways. + +via decorator: + +```TypeScript +import { Injectable } from '@nestjs/common'; +import { InjectCluster, DEFAULT_CLUSTER_NAMESPACE } from '@liaoliaots/nestjs-redis'; +import { Cluster } from 'ioredis'; + +@Injectable() +export class AppService { + constructor( + @InjectCluster() private readonly defaultClusterClient: Cluster + // or + // @InjectCluster(DEFAULT_CLUSTER_NAMESPACE) private readonly defaultClusterClient: Cluster + ) {} + + async ping(): Promise { + return await this.defaultClusterClient.ping(); + } +} +``` + +via manager: + +```TypeScript +import { Injectable } from '@nestjs/common'; +import { ClusterManager, DEFAULT_CLUSTER_NAMESPACE } from '@liaoliaots/nestjs-redis'; +import { Cluster } from 'ioredis'; + +@Injectable() +export class AppService { + private readonly defaultClusterClient: Cluster; + + constructor(private readonly clusterManager: ClusterManager) { + this.defaultClusterClient = this.clusterManager.getClient(); + // or + // this.defaultClusterClient = this.clusterManager.getClient(DEFAULT_CLUSTER_NAMESPACE); + } + + async ping(): Promise { + return await this.defaultClusterClient.ping(); + } +} +``` + +## Configuration + +### ClusterModuleOptions + +| Name | Type | Default value | Description | +| ----------- | ------------------------------------ | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| closeClient | boolean | false | If `true`, all clients will be closed automatically on nestjs application shutdown. To use `closeClient`, you **must enable listeners** by calling `app.enableShutdownHooks()`. [Read more about the application shutdown.](https://docs.nestjs.com/fundamentals/lifecycle-events#application-shutdown) | +| readyLog | boolean | false | If `true`, will show a message when the client is ready. | +| config | `ClientOptions` or `ClientOptions`[] | undefined | Specify single or multiple clients. | + +### ClientOptions + +| Name | Type | Default value | Description | +| --------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| namespace | string or symbol | Symbol('default') | The name of the client, and must be unique. You can import `DEFAULT_CLUSTER_NAMESPACE` to reference the default value. | +| [nodes](https://github.com/luin/ioredis/blob/master/API.md#new-clusterstartupnodes-options) | `{ host?: string; port?: number }[]` or `string[]` or `number[]` | undefined | A list of nodes of the cluster. The **first** argument of `new Cluster(startupNodes, options).` | +| [options](https://github.com/luin/ioredis/blob/master/API.md#new-clusterstartupnodes-options) | object | undefined | The [cluster options](https://github.com/luin/ioredis/blob/master/lib/cluster/ClusterOptions.ts#L30). The **second** argument of `new Cluster(startupNodes, options).` | +| onClientCreated | function | undefined | This function will be executed as soon as the client is created. | + +### Asynchronous configuration + +via `useFactory`: + +```TypeScript +import { Module } from '@nestjs/common'; +import { ClusterModule, ClusterModuleOptions } from '@liaoliaots/nestjs-redis'; +import { ConfigService, ConfigModule } from '@nestjs/config'; + +@Module({ + imports: [ + ClusterModule.forRootAsync({ + imports: [ConfigModule], + inject: [ConfigService], + useFactory: async (configService: ConfigService): Promise => { + await somePromise(); + + return { + closeClient: true, + config: { + nodes: [{ host: '127.0.0.1', port: 16380 }], + options: { redisOptions: { password: 'clusterpassword1' } } + } + }; + } + }) + ] +}) +export class AppModule {} +``` + +via `useClass`: + +```TypeScript +import { Module, Injectable } from '@nestjs/common'; +import { ClusterModule, ClusterOptionsFactory, ClusterModuleOptions } from '@liaoliaots/nestjs-redis'; + +@Injectable() +export class ClusterConfigService implements ClusterOptionsFactory { + async createClusterOptions(): Promise { + await somePromise(); + + return { + closeClient: true, + config: { + nodes: [{ host: '127.0.0.1', port: 16380 }], + options: { redisOptions: { password: 'clusterpassword1' } } + } + }; + } +} + +@Module({ + imports: [ + ClusterModule.forRootAsync({ + useClass: ClusterConfigService + }) + ] +}) +export class AppModule {} +``` + +via `extraProviders`: + +```TypeScript +// just a simple example + +import { Module, ValueProvider } from '@nestjs/common'; +import { ClusterModule, ClusterModuleOptions } from '@liaoliaots/nestjs-redis'; + +const MyOptionsSymbol = Symbol('options'); +const MyOptionsProvider: ValueProvider = { + provide: MyOptionsSymbol, + useValue: { + closeClient: true, + readyLog: true, + config: { + namespace: 'default', + nodes: [{ host: '127.0.0.1', port: 16380 }], + options: { redisOptions: { password: 'clusterpassword1' } } + } + } +}; + +@Module({ + imports: [ + ClusterModule.forRootAsync({ + useFactory(options: ClusterModuleOptions) { + return options; + }, + inject: [MyOptionsSymbol], + extraProviders: [MyOptionsProvider] + }) + ] +}) +export class AppModule {} +``` + +... or via `useExisting`, if you wish to use an existing configuration provider imported from a different module. + +```TypeScript +ClusterModule.forRootAsync({ + imports: [ConfigModule], + useExisting: ConfigService +}) +``` + +### readyLog + +```TypeScript +import { Module } from '@nestjs/common'; +import { ClusterModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [ + ClusterModule.forRoot({ + readyLog: true, + config: { + namespace: 'default', + nodes: [{ host: '127.0.0.1', port: 16380 }], + options: { redisOptions: { password: 'clusterpassword1' } } + } + }) + ] +}) +export class AppModule {} +``` + +The `ClusterModule` will display a message when `CLUSTER INFO` reporting the cluster is able to receive commands. + +```sh +[Nest] 18886 - 09/16/2021, 6:19:56 PM LOG [ClusterModule] default: Connected successfully to the server +``` + +### Single client + +```TypeScript +import { Module } from '@nestjs/common'; +import { ClusterModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [ + ClusterModule.forRoot({ + config: { + nodes: [{ host: '127.0.0.1', port: 16380 }], + options: { redisOptions: { password: 'clusterpassword1' } } + + // or with URL + // nodes: ['redis://:clusterpassword1@127.0.0.1:16380'] + } + }) + ] +}) +export class AppModule {} +``` + +### Multiple clients + +```TypeScript +import { Module } from '@nestjs/common'; +import { ClusterModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [ + ClusterModule.forRoot({ + config: [ + { + nodes: [{ host: '127.0.0.1', port: 16380 }], + options: { redisOptions: { password: 'clusterpassword1' } } + }, + { + namespace: 'cluster2', + nodes: [{ host: '127.0.0.1', port: 16480 }], + options: { redisOptions: { password: 'clusterpassword2' } } + } + ] + }) + ] +}) +export class AppModule {} +``` + +with URL: + +```TypeScript +import { Module } from '@nestjs/common'; +import { ClusterModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [ + ClusterModule.forRoot({ + config: [ + { + nodes: ['redis://:clusterpassword1@127.0.0.1:16380'] + }, + { + namespace: 'cluster2', + nodes: ['redis://:clusterpassword2@127.0.0.1:16480'] + } + ] + }) + ] +}) +export class AppModule {} +``` + +> HINT: If you don't set the namespace for a client, its namespace is set to default. Please note that you shouldn't have multiple client without a namespace, or with the same namespace, otherwise they will get overridden. + +### onClientCreated + +For example, we can listen to the error event of the cluster client. + +```TypeScript +import { Module } from '@nestjs/common'; +import { ClusterModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [ + ClusterModule.forRoot({ + config: { + nodes: [{ host: '127.0.0.1', port: 16380 }], + options: { redisOptions: { password: 'clusterpassword1' } }, + onClientCreated(client) { + client.on('error', err => {}); + } + } + }) + ] +}) +export class AppModule {} +``` diff --git a/docs/v4/dependency-graph.svg b/docs/v4/dependency-graph.svg new file mode 100644 index 00000000..2f9b9d6f --- /dev/null +++ b/docs/v4/dependency-graph.svg @@ -0,0 +1,469 @@ + + + + + + +G + + + +dist/cluster/cluster-manager.js + +dist/cluster/cluster-manager.js + + + +dist/cluster/cluster.constants.js + +dist/cluster/cluster.constants.js + + + +dist/cluster/cluster-manager.js->dist/cluster/cluster.constants.js + + + + + +dist/messages/index.js + +dist/messages/index.js + + + +dist/cluster/cluster-manager.js->dist/messages/index.js + + + + + +dist/utils/index.js + +dist/utils/index.js + + + +dist/cluster/cluster-manager.js->dist/utils/index.js + + + + + +dist/cluster/cluster.module.js + +dist/cluster/cluster.module.js + + + +dist/cluster/cluster.module.js->dist/cluster/cluster-manager.js + + + + + +dist/cluster/cluster.module.js->dist/cluster/cluster.constants.js + + + + + +dist/cluster/cluster.module.js->dist/messages/index.js + + + + + +dist/cluster/cluster.module.js->dist/utils/index.js + + + + + +dist/cluster/cluster.providers.js + +dist/cluster/cluster.providers.js + + + +dist/cluster/cluster.module.js->dist/cluster/cluster.providers.js + + + + + +dist/cluster/common/index.js + +dist/cluster/common/index.js + + + +dist/cluster/cluster.module.js->dist/cluster/common/index.js + + + + + +dist/cluster/cluster.providers.js->dist/cluster/cluster-manager.js + + + + + +dist/cluster/cluster.providers.js->dist/cluster/cluster.constants.js + + + + + +dist/cluster/cluster.providers.js->dist/cluster/common/index.js + + + + + +dist/cluster/default-options.js + +dist/cluster/default-options.js + + + +dist/cluster/cluster.providers.js->dist/cluster/default-options.js + + + + + +dist/cluster/common/cluster.decorator.js + +dist/cluster/common/cluster.decorator.js + + + +dist/cluster/common/index.js->dist/cluster/common/cluster.decorator.js + + + + + +dist/cluster/common/cluster.utils.js + +dist/cluster/common/cluster.utils.js + + + +dist/cluster/common/index.js->dist/cluster/common/cluster.utils.js + + + + + +dist/cluster/common/cluster.decorator.js->dist/cluster/cluster.constants.js + + + + + +dist/cluster/common/cluster.decorator.js->dist/utils/index.js + + + + + +dist/cluster/common/cluster.utils.js->dist/cluster/cluster.constants.js + + + + + +dist/cluster/common/cluster.utils.js->dist/messages/index.js + + + + + +dist/cluster/common/cluster.utils.js->dist/utils/index.js + + + + + +dist/health/index.js + +dist/health/index.js + + + +dist/health/indicators/redis.health.js + +dist/health/indicators/redis.health.js + + + +dist/health/index.js->dist/health/indicators/redis.health.js + + + + + +dist/health/redis-health.module.js + +dist/health/redis-health.module.js + + + +dist/health/index.js->dist/health/redis-health.module.js + + + + + +dist/health/indicators/redis.health.js->dist/messages/index.js + + + + + +dist/health/redis-health.module.js->dist/health/indicators/redis.health.js + + + + + +dist/index.js + +dist/index.js + + + +dist/index.js->dist/cluster/cluster-manager.js + + + + + +dist/index.js->dist/cluster/cluster.constants.js + + + + + +dist/index.js->dist/cluster/cluster.module.js + + + + + +dist/index.js->dist/cluster/common/index.js + + + + + +dist/redis/common/index.js + +dist/redis/common/index.js + + + +dist/index.js->dist/redis/common/index.js + + + + + +dist/redis/redis-manager.js + +dist/redis/redis-manager.js + + + +dist/index.js->dist/redis/redis-manager.js + + + + + +dist/redis/redis.constants.js + +dist/redis/redis.constants.js + + + +dist/index.js->dist/redis/redis.constants.js + + + + + +dist/redis/redis.module.js + +dist/redis/redis.module.js + + + +dist/index.js->dist/redis/redis.module.js + + + + + +dist/redis/common/redis.decorator.js + +dist/redis/common/redis.decorator.js + + + +dist/redis/common/index.js->dist/redis/common/redis.decorator.js + + + + + +dist/redis/common/redis.utils.js + +dist/redis/common/redis.utils.js + + + +dist/redis/common/index.js->dist/redis/common/redis.utils.js + + + + + +dist/redis/redis-manager.js->dist/messages/index.js + + + + + +dist/redis/redis-manager.js->dist/utils/index.js + + + + + +dist/redis/redis-manager.js->dist/redis/redis.constants.js + + + + + +dist/redis/redis.module.js->dist/messages/index.js + + + + + +dist/redis/redis.module.js->dist/utils/index.js + + + + + +dist/redis/redis.module.js->dist/redis/common/index.js + + + + + +dist/redis/redis.module.js->dist/redis/redis-manager.js + + + + + +dist/redis/redis.module.js->dist/redis/redis.constants.js + + + + + +dist/redis/redis.providers.js + +dist/redis/redis.providers.js + + + +dist/redis/redis.module.js->dist/redis/redis.providers.js + + + + + +dist/redis/common/redis.decorator.js->dist/utils/index.js + + + + + +dist/redis/common/redis.decorator.js->dist/redis/redis.constants.js + + + + + +dist/redis/common/redis.utils.js->dist/messages/index.js + + + + + +dist/redis/common/redis.utils.js->dist/utils/index.js + + + + + +dist/redis/common/redis.utils.js->dist/redis/redis.constants.js + + + + + +dist/redis/default-options.js + +dist/redis/default-options.js + + + +dist/redis/redis.providers.js->dist/redis/common/index.js + + + + + +dist/redis/redis.providers.js->dist/redis/redis-manager.js + + + + + +dist/redis/redis.providers.js->dist/redis/redis.constants.js + + + + + +dist/redis/redis.providers.js->dist/redis/default-options.js + + + + + +health/index.js + +health/index.js + + + +health/index.js->dist/health/index.js + + + + + diff --git a/docs/v4/examples.md b/docs/v4/examples.md new file mode 100644 index 00000000..e3a0c46b --- /dev/null +++ b/docs/v4/examples.md @@ -0,0 +1,151 @@ +# Examples + +## Redis + +### Default + +If the redis server does **not** have a password, the host is **127.0.0.1** and the port is **6379**: + +```TypeScript +import { Module } from '@nestjs/common'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [RedisModule.forRoot()] +}) +export class AppModule {} +``` + +... or + +```TypeScript +import { Module } from '@nestjs/common'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [RedisModule.forRoot({ closeClient: true })] +}) +export class AppModule {} +``` + +... or + +```TypeScript +import { Module } from '@nestjs/common'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [RedisModule.forRoot({ closeClient: true, config: { namespace: 'default' } })] +}) +export class AppModule {} +``` + +... or + +```TypeScript +import { Module } from '@nestjs/common'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [RedisModule.forRoot({ config: { host: '127.0.0.1', port: 6379 } })] +}) +export class AppModule {} +``` + +### Sentinel + +| name | ip | port | password | +| ------------------------ | --------- | ---- | ---------------- | +| master | 127.0.0.1 | 6380 | masterpassword1 | +| slave1 | 127.0.0.1 | 6480 | masterpassword1 | +| slave2 | 127.0.0.1 | 6481 | masterpassword1 | +| sentinel1 (**mymaster**) | 127.0.0.1 | 7380 | sentinelpassword | +| sentinel2 (**mymaster**) | 127.0.0.1 | 7381 | sentinelpassword | + +> HINT: When using Sentinel in Master-Slave setup, if you want to set the passwords for Master and Slave nodes, consider having the same password for them ([#7292](https://github.com/redis/redis/issues/7292)). + +```TypeScript +import { Module } from '@nestjs/common'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [ + RedisModule.forRoot({ + closeClient: true, + commonOptions: { + sentinels: [ + { + host: '127.0.0.1', + port: 7380 + }, + { + host: '127.0.0.1', + port: 7381 + } + ], + sentinelPassword: 'sentinelpassword', + password: 'masterpassword1' + }, + config: [ + // create a master from the sentinel group + { namespace: 'master node', name: 'mymaster', role: 'master' }, + // create a random slave from the sentinel group + { namespace: 'slave node', name: 'mymaster', role: 'slave' } + ] + }) + ] +}) +export class AppModule {} +``` + +> HINT: The `commonOptions` option works only with multiple clients. + +> INFO: Read more about sentinel [here](https://github.com/luin/ioredis#sentinel). + +## Cluster + +### Multiple Clients + +cluster 1: + +| name | ip | port | password | +| ------- | --------- | ----- | ---------------- | +| master1 | 127.0.0.1 | 16380 | clusterpassword1 | +| master2 | 127.0.0.1 | 16381 | clusterpassword1 | +| master3 | 127.0.0.1 | 16382 | clusterpassword1 | + +cluster 2: + +| name | ip | port | password | +| ------- | --------- | ----- | ---------------- | +| master1 | 127.0.0.1 | 16480 | clusterpassword2 | +| master2 | 127.0.0.1 | 16481 | clusterpassword2 | +| master3 | 127.0.0.1 | 16482 | clusterpassword2 | + +```TypeScript +import { Module } from '@nestjs/common'; +import { ClusterModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [ + ClusterModule.forRoot({ + closeClient: true, + config: [ + { + namespace: 'cluster1', + nodes: [{ host: '127.0.0.1', port: 16380 }], + options: { redisOptions: { password: 'clusterpassword1' } } + }, + { + namespace: 'cluster2', + nodes: [{ host: '127.0.0.1', port: 16480 }], + options: { redisOptions: { password: 'clusterpassword2' } } + } + ] + }) + ] +}) +export class AppModule {} +``` + +> INFO: Read more about cluster [here](https://github.com/luin/ioredis#cluster). diff --git a/docs/v4/health-checks.md b/docs/v4/health-checks.md new file mode 100644 index 00000000..6112ec9f --- /dev/null +++ b/docs/v4/health-checks.md @@ -0,0 +1,104 @@ +# Health Checks + +## Usage + +**Firstly**, we need to install the required package: + +```sh +$ npm install --save @nestjs/terminus +``` + +**Secondly**, we need to import the `TerminusModule` and `RedisHealthModule` into our business module: + +```TypeScript +import { Module } from '@nestjs/common'; +import { ClusterModule, RedisModule } from '@liaoliaots/nestjs-redis'; +import { RedisHealthModule } from '@liaoliaots/nestjs-redis/health'; // note the import path +import { TerminusModule } from '@nestjs/terminus'; +import { AppController } from './app.controller'; + +// Suppose we want to check health for redis and cluster, so we need to import the `ClusterModule` and `RedisModule`. + +@Module({ + imports: [ + ClusterModule.forRoot({ + closeClient: true, + config: { + nodes: [{ host: '127.0.0.1', port: 16380 }], + options: { redisOptions: { password: 'clusterpassword1' } } + } + }), + RedisModule.forRoot({ + closeClient: true, + config: { + host: '127.0.0.1', + port: 6380, + password: 'masterpassword1' + } + }), + TerminusModule, + RedisHealthModule + ], + controllers: [AppController] +}) +export class AppModule {} +``` + +> HINT: Both `TerminusModule` and `RedisHealthModule` aren't global modules. + +**Now** let's create a health check: + +```TypeScript +import { Controller, Get } from '@nestjs/common'; +import { HealthCheckService, HealthCheck, HealthCheckResult } from '@nestjs/terminus'; +import { InjectRedis, InjectCluster } from '@liaoliaots/nestjs-redis'; +import { RedisHealthIndicator } from '@liaoliaots/nestjs-redis/health'; // note the import path +import { Redis, Cluster } from 'ioredis'; + +@Controller() +export class AppController { + constructor( + private readonly health: HealthCheckService, + private readonly redis: RedisHealthIndicator, + @InjectRedis() private readonly defaultRedisClient: Redis, + @InjectCluster() private readonly defaultClusterClient: Cluster + ) {} + + @Get('health') + @HealthCheck() + async healthChecks(): Promise { + return await this.health.check([ + () => this.redis.checkHealth('default-redis-client', { client: this.defaultRedisClient }), + () => this.redis.checkHealth('default-cluster-client', { client: this.defaultClusterClient }) + ]); + } +} + +``` + +If your redis and cluster are reachable, you should now see the following JSON-result when requesting http://localhost:3000/health with a GET request: + +```json +{ + "status": "ok", + "info": { + "default-redis-client": { + "status": "up" + }, + "default-cluster-client": { + "status": "up" + } + }, + "error": {}, + "details": { + "default-redis-client": { + "status": "up" + }, + "default-cluster-client": { + "status": "up" + } + } +} +``` + +> INFO: Read more about `@nestjs/terminus` [here](https://docs.nestjs.com/recipes/terminus). diff --git a/docs/v4/redis.md b/docs/v4/redis.md new file mode 100644 index 00000000..0eac082f --- /dev/null +++ b/docs/v4/redis.md @@ -0,0 +1,440 @@ +# Redis + +## Usage + +**Firstly**, we need to import the `RedisModule` into our root module: + +```TypeScript +import { Module } from '@nestjs/common'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [ + RedisModule.forRoot({ + closeClient: true, + config: { + host: '127.0.0.1', + port: 6380, + password: 'masterpassword1' + } + }) + ] +}) +export class AppModule {} +``` + +> HINT: The `RedisModule` is a [global module](https://docs.nestjs.com/modules#global-modules). Once defined, this module is available everywhere. + +**Now** we can use redis in two ways. + +via decorator: + +```TypeScript +import { Injectable } from '@nestjs/common'; +import { InjectRedis, DEFAULT_REDIS_NAMESPACE } from '@liaoliaots/nestjs-redis'; +import { Redis } from 'ioredis'; + +@Injectable() +export class AppService { + constructor( + @InjectRedis() private readonly defaultRedisClient: Redis + // or + // @InjectRedis(DEFAULT_REDIS_NAMESPACE) private readonly defaultRedisClient: Redis + ) {} + + async ping(): Promise { + return await this.defaultRedisClient.ping(); + } +} +``` + +via manager: + +```TypeScript +import { Injectable } from '@nestjs/common'; +import { RedisManager, DEFAULT_REDIS_NAMESPACE } from '@liaoliaots/nestjs-redis'; +import { Redis } from 'ioredis'; + +@Injectable() +export class AppService { + private readonly defaultRedisClient: Redis; + + constructor(private readonly redisManager: RedisManager) { + this.defaultRedisClient = this.redisManager.getClient(); + // or + // this.defaultRedisClient = this.redisManager.getClient(DEFAULT_REDIS_NAMESPACE); + } + + async ping(): Promise { + return await this.defaultRedisClient.ping(); + } +} +``` + +### Use with other libraries that depend on redis + +For example, use with `@nestjs/throttler` and `nestjs-throttler-storage-redis`: + +```TypeScript +import { Module } from '@nestjs/common'; +import { APP_GUARD } from '@nestjs/core'; +import { RedisModule, RedisManager } from '@liaoliaots/nestjs-redis'; +import { ThrottlerGuard, ThrottlerModule } from '@nestjs/throttler'; +import { ThrottlerStorageRedisService } from 'nestjs-throttler-storage-redis'; + +@Module({ + imports: [ + RedisModule.forRoot({ + closeClient: true, + readyLog: true, + config: { + namespace: 'default', + host: '127.0.0.1', + port: 6380, + password: 'masterpassword1' + } + }), + ThrottlerModule.forRootAsync({ + useFactory(redisManager: RedisManager) { + const redis = redisManager.getClient('default'); + return { ttl: 60, limit: 10, storage: new ThrottlerStorageRedisService(redis) }; + }, + inject: [RedisManager] + }) + ], + providers: [ + { + provide: APP_GUARD, + useClass: ThrottlerGuard + } + ] +}) +export class AppModule {} +``` + +## Configuration + +### RedisModuleOptions + +| Name | Type | Default value | Description | +| ------------- | ------------------------------------ | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| closeClient | boolean | false | If `true`, all clients will be closed automatically on nestjs application shutdown. To use `closeClient`, you **must enable listeners** by calling `app.enableShutdownHooks()`. [Read more about the application shutdown.](https://docs.nestjs.com/fundamentals/lifecycle-events#application-shutdown) | +| commonOptions | object | undefined | The common options for each client. | +| readyLog | boolean | false | If `true`, will show a message when the client is ready. | +| config | `ClientOptions` or `ClientOptions`[] | { host: 'localhost', port: 6379 } | Specify single or multiple clients. | + +### ClientOptions + +| Name | Type | Default value | Description | +| ---------------------------------------------------------------------------------------------------- | ---------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| namespace | string or symbol | Symbol('default') | The name of the client, and must be unique. You can import `DEFAULT_REDIS_NAMESPACE` to reference the default value. | +| url | string | undefined | The URL([redis://](https://www.iana.org/assignments/uri-schemes/prov/redis) or [rediss://](https://www.iana.org/assignments/uri-schemes/prov/rediss)) specifies connection options. | +| onClientCreated | function | undefined | This function will be executed as soon as the client is created. | +| **...**[RedisOptions](https://github.com/luin/ioredis/blob/master/API.md#new-redisport-host-options) | object | - | Extends the [RedisOptions](https://github.com/luin/ioredis/blob/master/lib/redis/RedisOptions.ts#L8). | + +### Asynchronous configuration + +via `useFactory`: + +```TypeScript +import { Module } from '@nestjs/common'; +import { RedisModule, RedisModuleOptions } from '@liaoliaots/nestjs-redis'; +import { ConfigService, ConfigModule } from '@nestjs/config'; + +@Module({ + imports: [ + RedisModule.forRootAsync({ + imports: [ConfigModule], + inject: [ConfigService], + useFactory: async (configService: ConfigService): Promise => { + await somePromise(); + + return { + closeClient: true, + config: { + host: '127.0.0.1', + port: 6380, + password: 'masterpassword1' + } + }; + } + }) + ] +}) +export class AppModule {} +``` + +via `useClass`: + +```TypeScript +import { Module, Injectable } from '@nestjs/common'; +import { RedisModule, RedisOptionsFactory, RedisModuleOptions } from '@liaoliaots/nestjs-redis'; + +@Injectable() +export class RedisConfigService implements RedisOptionsFactory { + async createRedisOptions(): Promise { + await somePromise(); + + return { + closeClient: true, + config: { + host: '127.0.0.1', + port: 6380, + password: 'masterpassword1' + } + }; + } +} + +@Module({ + imports: [ + RedisModule.forRootAsync({ + useClass: RedisConfigService + }) + ] +}) +export class AppModule {} +``` + +via `extraProviders`: + +```TypeScript +// just a simple example + +import { Module, ValueProvider } from '@nestjs/common'; +import { RedisModule, RedisModuleOptions } from '@liaoliaots/nestjs-redis'; + +const MyOptionsSymbol = Symbol('options'); +const MyOptionsProvider: ValueProvider = { + provide: MyOptionsSymbol, + useValue: { + closeClient: true, + readyLog: true, + config: { + namespace: 'default', + host: '127.0.0.1', + port: 6380, + password: 'masterpassword1' + } + } +}; + +@Module({ + imports: [ + RedisModule.forRootAsync({ + useFactory(options: RedisModuleOptions) { + return options; + }, + inject: [MyOptionsSymbol], + extraProviders: [MyOptionsProvider] + }) + ] +}) +export class AppModule {} +``` + +... or via `useExisting`, if you wish to use an existing configuration provider imported from a different module. + +```TypeScript +RedisModule.forRootAsync({ + imports: [ConfigModule], + useExisting: ConfigService +}) +``` + +### readyLog + +```TypeScript +import { Module } from '@nestjs/common'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [ + RedisModule.forRoot({ + readyLog: true, + config: { + namespace: 'default', + host: '127.0.0.1', + port: 6380, + password: 'masterpassword1' + } + }) + ] +}) +export class AppModule {} +``` + +The `RedisModule` will display a message when the server reports that it is ready to receive commands. + +```sh +[Nest] 17581 - 09/16/2021, 6:03:35 PM LOG [RedisModule] default: Connected successfully to the server +``` + +### Single client + +```TypeScript +import { Module } from '@nestjs/common'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [ + RedisModule.forRoot({ + config: { + host: '127.0.0.1', + port: 6380, + password: 'masterpassword1' + + // or with URL + // url: 'redis://:masterpassword1@127.0.0.1:6380/0' + } + }) + ] +}) +export class AppModule {} +``` + +### Multiple clients + +```TypeScript +import { Module } from '@nestjs/common'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [ + RedisModule.forRoot({ + config: [ + { + host: '127.0.0.1', + port: 6380, + password: 'masterpassword1' + }, + { + namespace: 'master2', + host: '127.0.0.1', + port: 6381, + password: 'masterpassword2' + } + ] + }) + ] +}) +export class AppModule {} +``` + +with URL: + +```TypeScript +import { Module } from '@nestjs/common'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [ + RedisModule.forRoot({ + config: [ + { + url: 'redis://:masterpassword1@127.0.0.1:6380/0' + }, + { + namespace: 'master2', + url: 'redis://:masterpassword2@127.0.0.1:6381/0' + } + ] + }) + ] +}) +export class AppModule {} +``` + +> HINT: If you don't set the namespace for a client, its namespace is set to default. Please note that you shouldn't have multiple client without a namespace, or with the same namespace, otherwise they will get overridden. + +### commonOptions + +**In some cases**, you can move the same config of multiple clients to `commonOptions`. + +> HINT: The `commonOptions` option works only with multiple clients. + +```TypeScript +import { Module } from '@nestjs/common'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [ + RedisModule.forRoot({ + commonOptions: { + enableAutoPipelining: true + }, + config: [ + { + host: '127.0.0.1', + port: 6380, + password: 'masterpassword1' + }, + { + namespace: 'master2', + host: '127.0.0.1', + port: 6381, + password: 'masterpassword2' + } + ] + }) + ] +}) +export class AppModule {} +``` + +You can also override the `commonOptions`: + +```TypeScript +import { Module } from '@nestjs/common'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [ + RedisModule.forRoot({ + commonOptions: { + enableAutoPipelining: true + }, + config: [ + { + host: '127.0.0.1', + port: 6380, + password: 'masterpassword1' + }, + { + namespace: 'master2', + host: '127.0.0.1', + port: 6381, + password: 'masterpassword2', + enableAutoPipelining: false + } + ] + }) + ] +}) +export class AppModule {} +``` + +### onClientCreated + +For example, we can listen to the error event of the redis client. + +```TypeScript +import { Module } from '@nestjs/common'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; + +@Module({ + imports: [ + RedisModule.forRoot({ + config: { + host: '127.0.0.1', + port: 6380, + password: 'masterpassword1', + onClientCreated(client) { + client.on('error', err => {}); + } + } + }) + ] +}) +export class AppModule {} +``` diff --git a/packages/redis/README.md b/packages/redis/README.md index 5aee00fb..1420309c 100644 --- a/packages/redis/README.md +++ b/packages/redis/README.md @@ -259,5 +259,5 @@ Distributed under the MIT License. See `LICENSE` for more information. [license-shield]: https://img.shields.io/npm/l/@liaoliaots/nestjs-redis?style=for-the-badge [license-url]: https://github.com/liaoliaots/nestjs-redis/blob/main/LICENSE [vulnerabilities-shield]: https://img.shields.io/snyk/vulnerabilities/npm/@liaoliaots/nestjs-redis?style=for-the-badge -[workflow-shield]: https://img.shields.io/github/workflow/status/liaoliaots/nestjs-redis/testing?label=TESTING&style=for-the-badge +[workflow-shield]: https://img.shields.io/github/actions/workflow/status/liaoliaots/nestjs-redis/testing.yaml?label=TESTING&style=for-the-badge [workflow-url]: https://github.com/liaoliaots/nestjs-redis/actions/workflows/testing.yaml