Skip to content

Commit

Permalink
refactor!: rewrite redis * cluster (#550)
Browse files Browse the repository at this point in the history
  • Loading branch information
liaoliaots authored Sep 25, 2024
1 parent cb18450 commit 13e056f
Show file tree
Hide file tree
Showing 44 changed files with 447 additions and 888 deletions.
16 changes: 8 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@
"devDependencies": {
"@commitlint/cli": "19.5.0",
"@commitlint/config-conventional": "19.5.0",
"@eslint/js": "9.11.0",
"@nestjs/common": "10.4.3",
"@nestjs/core": "10.4.3",
"@nestjs/platform-fastify": "10.4.3",
"@nestjs/testing": "10.4.3",
"@eslint/js": "9.11.1",
"@nestjs/common": "10.4.4",
"@nestjs/core": "10.4.4",
"@nestjs/platform-fastify": "10.4.4",
"@nestjs/testing": "10.4.4",
"@tsconfig/node20": "20.1.4",
"@types/eslint__js": "8.42.3",
"@types/jest": "29.5.13",
"@types/node": "20.16.5",
"@types/node": "20.16.7",
"concurrently": "9.0.1",
"eslint": "9.11.0",
"eslint": "9.11.1",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-jest": "28.8.3",
"eslint-plugin-prettier": "5.2.1",
Expand All @@ -39,7 +39,7 @@
"ts-jest": "29.2.5",
"tsc-alias": "1.8.10",
"typescript": "5.6.2",
"typescript-eslint": "8.6.0"
"typescript-eslint": "8.7.0"
},
"engines": {
"node": ">=20",
Expand Down
File renamed without changes.
98 changes: 4 additions & 94 deletions packages/redis/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
- **Both redis & cluster are supported**: You can also specify multiple instances.
- **Health**: Checks health of **redis & cluster** server.
- **Rigorously tested**: With 100+ tests and 100% code coverage.
- **Services**: Retrieves **redis & cluster** clients via `RedisManager`, `ClusterManager`.
- **Services**: Retrieves **redis & cluster** connection via `RedisService`, `ClusterService`.

### Test coverage

Expand All @@ -76,8 +76,8 @@ This lib requires **Node.js >=16.13.0**, **NestJS ^10.0.0**, **ioredis ^5.0.0**.

- If you depend on **ioredis 5** & **NestJS 10**, please use version **10** of the lib.
- If you depend on **ioredis 5** & **NestJS 9**, please use version **9** of the lib.
- If you depend on **ioredis 4**, please use [version 7](https://github.com/liaoliaots/nestjs-redis/tree/v7.0.0) of the lib.
- If you depend on **ioredis 5**, **NestJS 7** or **8**, please use [version 8](https://github.com/liaoliaots/nestjs-redis/tree/v8.2.2) of the lib.
- If you depend on **ioredis 4**, please use [version 7](https://github.com/liaoliaots/nestjs-redis/tree/v7.0.0) of the lib.

### Installation

Expand Down Expand Up @@ -111,8 +111,9 @@ pnpm add @liaoliaots/nestjs-redis ioredis

### Legacy

- version 7, [click here](/docs/v7)
- version 9, [click here](/docs/v9)
- version 8, [click here](/docs/v8)
- version 7, [click here](/docs/v7)

## FAQs

Expand All @@ -125,97 +126,6 @@ pnpm add @liaoliaots/nestjs-redis ioredis

</details>

### "Cannot resolve dependency" error

<details>
<summary>Click to expand</summary>

If you encountered an error like this:

```
Nest can't resolve dependencies of the <provider> (?). Please make sure that the argument <unknown_token> at index [<index>] is available in the <module> context.
Potential solutions:
- If <unknown_token> is a provider, is it part of the current <module>?
- If <unknown_token> is exported from a separate @Module, is that module imported within <module>?
@Module({
imports: [ /* the Module containing <unknown_token> */ ]
})
```

Please make sure that the `RedisModule` is added directly to the `imports` array of `@Module()` decorator of "Root Module"(if `isGlobal` is true) or "Feature Module"(if `isGlobal` is false).

Examples of code:

```ts
// redis-config.service.ts
import { Injectable } from '@nestjs/common';
import { RedisModuleOptions, RedisOptionsFactory } from '@liaoliaots/nestjs-redis';

@Injectable()
export class RedisConfigService implements RedisOptionsFactory {
createRedisOptions(): RedisModuleOptions {
return {
readyLog: true,
config: {
host: 'localhost',
port: 6379,
password: 'authpassword'
}
};
}
}
```

### ✅ Correct

```ts
// app.module.ts
import { Module } from '@nestjs/common';
import { RedisModule } from '@liaoliaots/nestjs-redis';
import { RedisConfigService } from './redis-config.service';

@Module({
imports: [
RedisModule.forRootAsync({
useClass: RedisConfigService
})
]
})
export class AppModule {}
```

### ❌ Incorrect

```ts
// my-redis.module.ts
import { Module } from '@nestjs/common';
import { RedisModule } from '@liaoliaots/nestjs-redis';
import { RedisConfigService } from './redis-config.service';

@Module({
imports: [
RedisModule.forRootAsync({
useClass: RedisConfigService
})
]
})
export class MyRedisModule {}
```

```ts
// app.module.ts
import { Module } from '@nestjs/common';
import { MyRedisModule } from './my-redis.module';

@Module({
imports: [MyRedisModule]
})
export class AppModule {}
```

</details>

## Contributing

Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**.
Expand Down
33 changes: 0 additions & 33 deletions packages/redis/lib/cluster/cluster-manager.ts

This file was deleted.

5 changes: 4 additions & 1 deletion packages/redis/lib/cluster/cluster.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ export const CLUSTER_MERGED_OPTIONS = Symbol();

export const CLUSTER_CLIENTS = Symbol();

export const DEFAULT_CLUSTER_NAMESPACE = 'default';
/**
* The default cluster namespace.
*/
export const DEFAULT_CLUSTER = 'default';

export const CLUSTER_MODULE_ID = 'ClusterModule';

Expand Down
48 changes: 27 additions & 21 deletions packages/redis/lib/cluster/cluster.module.ts
Original file line number Diff line number Diff line change
@@ -1,64 +1,63 @@
import { Module, DynamicModule, Provider, OnApplicationShutdown } from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
import { ClusterModuleOptions, ClusterModuleAsyncOptions, ClusterClients } from './interfaces';
import { ClusterManager } from './cluster-manager';
import { ClusterService } from './cluster.service';
import {
createOptionsProvider,
createAsyncProviders,
createClusterClientProviders,
clusterClientsProvider,
mergedOptionsProvider
} from './cluster.providers';
import { CLUSTER_CLIENTS, CLUSTER_MERGED_OPTIONS } from './cluster.constants';
import { destroy } from './common';
import { parseNamespace, isResolution, isRejection, isError } from '@/utils';
import { parseNamespace, isError } from '@/utils';
import { logger } from './cluster-logger';
import { MissingConfigurationsError } from '@/errors';
import { ERROR_LOG } from '@/messages';

/**
* @public
*/
@Module({})
export class ClusterModule implements OnApplicationShutdown {
constructor(private moduleRef: ModuleRef) {}

/**
* Registers the module synchronously.
*
* @param options - The module options
* @param isGlobal - Register in the global scope
* @returns A DynamicModule
*/
static forRoot(options: ClusterModuleOptions, isGlobal = true): DynamicModule {
const clusterClientProviders = createClusterClientProviders();
const providers: Provider[] = [
createOptionsProvider(options),
clusterClientsProvider,
mergedOptionsProvider,
ClusterManager,
...clusterClientProviders
ClusterService
];

return {
global: isGlobal,
module: ClusterModule,
providers,
exports: [ClusterManager, ...clusterClientProviders]
exports: [ClusterService]
};
}

/**
* Registers the module asynchronously.
*
* @param options - The async module options
* @param isGlobal - Register in the global scope
* @returns A DynamicModule
*/
static forRootAsync(options: ClusterModuleAsyncOptions, isGlobal = true): DynamicModule {
if (!options.useFactory && !options.useClass && !options.useExisting) {
throw new MissingConfigurationsError();
}

const clusterClientProviders = createClusterClientProviders();
const providers: Provider[] = [
...createAsyncProviders(options),
clusterClientsProvider,
mergedOptionsProvider,
ClusterManager,
...clusterClientProviders,
ClusterService,
...(options.extraProviders ?? [])
];

Expand All @@ -67,19 +66,26 @@ export class ClusterModule implements OnApplicationShutdown {
module: ClusterModule,
imports: options.imports,
providers,
exports: [ClusterManager, ...clusterClientProviders]
exports: [ClusterService]
};
}

async onApplicationShutdown(): Promise<void> {
const { closeClient } = this.moduleRef.get<ClusterModuleOptions>(CLUSTER_MERGED_OPTIONS);
const { closeClient } = this.moduleRef.get<ClusterModuleOptions>(CLUSTER_MERGED_OPTIONS, { strict: false });
if (closeClient) {
const results = await destroy(this.moduleRef.get<ClusterClients>(CLUSTER_CLIENTS));
results.forEach(([namespace, quit]) => {
if (isResolution(namespace) && isRejection(quit) && isError(quit.reason)) {
logger.error(ERROR_LOG(parseNamespace(namespace.value), quit.reason.message), quit.reason.stack);
const clients = this.moduleRef.get<ClusterClients>(CLUSTER_CLIENTS, { strict: false });
for (const [namespace, client] of clients) {
if (client.status === 'end') continue;
if (client.status === 'ready') {
try {
await client.quit();
} catch (e) {
if (isError(e)) logger.error(ERROR_LOG(parseNamespace(namespace), e.message), e.stack);
}
continue;
}
});
client.disconnect();
}
}
}
}
27 changes: 4 additions & 23 deletions packages/redis/lib/cluster/cluster.providers.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
import { Provider, FactoryProvider, ValueProvider } from '@nestjs/common';
import type { Cluster } from 'ioredis';
import { ClusterModuleOptions, ClusterModuleAsyncOptions, ClusterOptionsFactory, ClusterClients } from './interfaces';
import {
CLUSTER_OPTIONS,
CLUSTER_CLIENTS,
DEFAULT_CLUSTER_NAMESPACE,
CLUSTER_MERGED_OPTIONS
} from './cluster.constants';
import { createClient, namespaces } from './common';
import { ClusterManager } from './cluster-manager';
import { CLUSTER_OPTIONS, CLUSTER_CLIENTS, DEFAULT_CLUSTER, CLUSTER_MERGED_OPTIONS } from './cluster.constants';
import { createClient } from './common';
import { defaultClusterModuleOptions } from './default-options';

export const createOptionsProvider = (options: ClusterModuleOptions): ValueProvider<ClusterModuleOptions> => ({
Expand Down Expand Up @@ -67,32 +60,20 @@ export const createAsyncOptionsProvider = (options: ClusterModuleAsyncOptions):
};
};

export const createClusterClientProviders = (): FactoryProvider<Cluster>[] => {
const providers: FactoryProvider<Cluster>[] = [];
namespaces.forEach((token, namespace) => {
providers.push({
provide: token,
useFactory: (clusterManager: ClusterManager) => clusterManager.getClient(namespace),
inject: [ClusterManager]
});
});
return providers;
};

export const clusterClientsProvider: FactoryProvider<ClusterClients> = {
provide: CLUSTER_CLIENTS,
useFactory: (options: ClusterModuleOptions) => {
const clients: ClusterClients = new Map();
if (Array.isArray(options.config)) {
options.config.forEach(item =>
clients.set(
item.namespace ?? DEFAULT_CLUSTER_NAMESPACE,
item.namespace ?? DEFAULT_CLUSTER,
createClient(item, { readyLog: options.readyLog, errorLog: options.errorLog })
)
);
} else if (options.config) {
clients.set(
options.config.namespace ?? DEFAULT_CLUSTER_NAMESPACE,
options.config.namespace ?? DEFAULT_CLUSTER,
createClient(options.config, { readyLog: options.readyLog, errorLog: options.errorLog })
);
}
Expand Down
Loading

0 comments on commit 13e056f

Please sign in to comment.