Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor!: rewrite redis * cluster #550

Merged
merged 2 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading