@xnestjs/ioredis 1.2.5 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,142 @@
1
1
  # @xnestjs/ioredis
2
2
 
3
3
  NestJS extension library for ioredis
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ npm install @xnestjs/ioredis
9
+ # or using yarn
10
+ yarn add @xnestjs/ioredis
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ### Register standalone sync
16
+
17
+ An example of nestjs module that creates RedisClient with standalone connection.
18
+
19
+ ```ts
20
+ // module.ts
21
+ import { Module } from '@nestjs/common';
22
+ import { RedisModule } from '@xnestjs/ioredis';
23
+
24
+ @Module({
25
+ imports: [
26
+ RedisModule.forRoot({
27
+ useValue: {
28
+ host: 'localhost',
29
+ port: 6379
30
+ }
31
+ }),
32
+ ]
33
+ })
34
+ export class MyModule {
35
+ }
36
+ ```
37
+
38
+ ### Register cluster sync
39
+
40
+ An example of nestjs module that creates RedisClient with cluster connection.
41
+
42
+ ```ts
43
+ // module.ts
44
+ import { Module } from '@nestjs/common';
45
+ import { RedisModule } from '@xnestjs/ioredis';
46
+
47
+ @Module({
48
+ imports: [
49
+ RedisModule.forRoot({
50
+ useValue: {
51
+ nodes: ['localhost'],
52
+ }
53
+ }),
54
+ ]
55
+ })
56
+ export class MyModule {
57
+ }
58
+ ```
59
+
60
+ ### Register standalone async
61
+
62
+ An example of nestjs module that creates RedisClient with standalone connection async
63
+
64
+ ```ts
65
+ // module.ts
66
+ import { Module } from '@nestjs/common';
67
+ import { RedisModule } from '@xnestjs/ioredis';
68
+
69
+ @Module({
70
+ imports: [
71
+ ElasticsearchModule.forRootAsync({
72
+ inject: [ConfigModule],
73
+ useFactory: (config: ConfigService) => ({
74
+ host: config.get('REDIS_HOST'),
75
+ }),
76
+ }),
77
+ ]
78
+ })
79
+ export class MyModule {
80
+ }
81
+ ```
82
+
83
+ ### Register cluster async
84
+
85
+ An example of nestjs module that creates RedisClient with cluster connection async
86
+
87
+ ```ts
88
+ // module.ts
89
+ import { Module } from '@nestjs/common';
90
+ import { RedisModule } from '@xnestjs/ioredis';
91
+
92
+ @Module({
93
+ imports: [
94
+ ElasticsearchModule.forRootAsync({
95
+ inject: [ConfigModule],
96
+ useFactory: (config: ConfigService) => ({
97
+ nodes: config.get('REDIS_NODES'),
98
+ }),
99
+ }),
100
+ ]
101
+ })
102
+ export class MyModule {
103
+ }
104
+ ```
105
+
106
+ ## Environment Variables
107
+
108
+ The library supports configuration through environment variables. Environment variables below is accepted.
109
+ All environment variables starts with prefix (REDIS_). This can be configured while registering the module.
110
+
111
+ ### Standalone Connection Variables
112
+
113
+ The following environment variables apply to the standalone connection.
114
+
115
+ | Environment Variable | Type | Default | Description |
116
+ |----------------------|--------|-----------|-------------|
117
+ | REDIS_HOST | String | localhost | |
118
+ | REDIS_PORT | Number | 6379 | |
119
+
120
+ ### Cluster Connection Variables
121
+
122
+ The following environment variables apply to the standalone connection.
123
+
124
+ | Environment Variable | Type | Default | Description |
125
+ |----------------------|---------|-----------|-------------|
126
+ | NODES | String! | localhost | |
127
+
128
+ ### Common Variables
129
+
130
+ | Environment Variable | Type | Default | Description |
131
+ |-------------------------------|---------|---------|-----------------------------------------------------------------------------------------------------------------------------------|
132
+ | REDIS_DB | Number | 0 | Hostname for Redis Server |
133
+ | REDIS_USERNAME | String | | Port number |
134
+ | REDIS_PASSWORD | String | | If set, client will send AUTH command with the value of this option as the first argument when connected. |
135
+ | REDIS_CONNECTION_NAME | String | | If set, client will send AUTH command with the value of this option when connected. |
136
+ | REDIS_AUTO_RESUBSCRIBE | Boolean | | When the client reconnects, channels subscribed in the previous connection will be resubscribed automatically if value is `true`. |
137
+ | REDIS_RECONNECT_ON_ERROR | Number | | One of [`0`, `1`, `2`] |
138
+ | REDIS_CONNECT_TIMEOUT | Number | 10000 | How long the client will wait before killing a socket due to inactivity during initial connection. |
139
+ | REDIS_SOCKET_TIMEOUT | Number | | Defines the socket timeout value |
140
+ | REDIS_KEEP_ALIVE | Boolean | | Enable/disable keep-alive functionality. |
141
+ | REDIS_NO_DELAY | Boolean | | Enable/disable the use of Nagle's algorithm. |
142
+ | REDIS_MAX_RETRIES_PER_REQUEST | Number | | Defines max retries per request value |
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.IOREDIS_MODULE_TOKEN = exports.IOREDIS_CONNECTION_OPTIONS = void 0;
4
+ exports.IOREDIS_CONNECTION_OPTIONS = Symbol('IOREDIS_CONNECTION_OPTIONS');
5
+ exports.IOREDIS_MODULE_TOKEN = Symbol('IOREDIS_MODULE_ID');
package/cjs/index.js CHANGED
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const tslib_1 = require("tslib");
4
- tslib_1.__exportStar(require("./redis.interface.js"), exports);
5
4
  tslib_1.__exportStar(require("./redis.module.js"), exports);
6
5
  tslib_1.__exportStar(require("./redis-client.js"), exports);
7
6
  tslib_1.__exportStar(require("./shared-lock.js"), exports);
@@ -3,105 +3,164 @@ var RedisCoreModule_1;
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.RedisCoreModule = void 0;
5
5
  const tslib_1 = require("tslib");
6
+ const node_assert_1 = tslib_1.__importDefault(require("node:assert"));
6
7
  const node_process_1 = tslib_1.__importDefault(require("node:process"));
8
+ const objects_1 = require("@jsopen/objects");
7
9
  const common_1 = require("@nestjs/common");
8
- const core_1 = require("@nestjs/core");
9
10
  const ansi_colors_1 = tslib_1.__importDefault(require("ansi-colors"));
10
11
  const crypto = tslib_1.__importStar(require("crypto"));
11
12
  const ioredis_1 = tslib_1.__importStar(require("ioredis"));
12
- const redis_constants_js_1 = require("./redis.constants.js");
13
+ const putil_varhelpers_1 = require("putil-varhelpers");
14
+ const constants_js_1 = require("./constants.js");
13
15
  const redis_client_js_1 = require("./redis-client.js");
14
16
  const utils_js_1 = require("./utils.js");
17
+ const CLIENT_TOKEN = Symbol('CLIENT_TOKEN');
15
18
  let RedisCoreModule = RedisCoreModule_1 = class RedisCoreModule {
16
- constructor(options, moduleRef) {
17
- this.options = options;
18
- this.moduleRef = moduleRef;
19
- }
20
- static forRoot(options) {
21
- const optionsProvider = {
22
- provide: redis_constants_js_1.IOREDIS_MODULE_OPTIONS,
23
- useValue: options,
24
- };
25
- const token = options.token || redis_client_js_1.RedisClient;
26
- const connectionProvider = {
27
- provide: token,
28
- useFactory: () => this._createClient(options),
29
- };
30
- return {
31
- module: RedisCoreModule_1,
32
- providers: [connectionProvider, optionsProvider],
33
- exports: [connectionProvider],
34
- };
19
+ static forRoot(moduleOptions) {
20
+ const connectionOptions = this._readConnectionOptions(moduleOptions.useValue, moduleOptions.envPrefix);
21
+ return this._createDynamicModule(moduleOptions, {
22
+ global: moduleOptions.global,
23
+ providers: [
24
+ {
25
+ provide: constants_js_1.IOREDIS_CONNECTION_OPTIONS,
26
+ useValue: connectionOptions,
27
+ },
28
+ ],
29
+ });
35
30
  }
36
31
  static forRootAsync(asyncOptions) {
37
- if (!asyncOptions.useFactory)
38
- throw new Error('Invalid configuration. Must provide "useFactory"');
39
- const token = asyncOptions.token || redis_client_js_1.RedisClient;
40
- const connectionProvider = {
41
- provide: token,
42
- inject: [redis_constants_js_1.IOREDIS_MODULE_OPTIONS],
43
- useFactory: async (moduleOptions) => this._createClient(moduleOptions),
44
- };
45
- return {
46
- module: RedisCoreModule_1,
47
- imports: asyncOptions.imports,
32
+ node_assert_1.default.ok(asyncOptions.useFactory, 'useFactory is required');
33
+ return this._createDynamicModule(asyncOptions, {
34
+ global: asyncOptions.global,
48
35
  providers: [
49
36
  {
50
- provide: redis_constants_js_1.IOREDIS_MODULE_OPTIONS,
51
- useFactory: asyncOptions.useFactory,
52
- inject: asyncOptions.inject || [],
37
+ provide: constants_js_1.IOREDIS_CONNECTION_OPTIONS,
38
+ inject: asyncOptions.inject,
39
+ useFactory: async (...args) => {
40
+ const opts = await asyncOptions.useFactory(...args);
41
+ return this._readConnectionOptions(opts, asyncOptions.envPrefix);
42
+ },
53
43
  },
44
+ ],
45
+ });
46
+ }
47
+ static _createDynamicModule(opts, metadata) {
48
+ const token = opts.token ?? redis_client_js_1.RedisClient;
49
+ const providers = [
50
+ {
51
+ provide: token,
52
+ inject: [constants_js_1.IOREDIS_CONNECTION_OPTIONS],
53
+ useFactory: async (connectionOptions) => {
54
+ return this._createClient(connectionOptions);
55
+ },
56
+ },
57
+ {
58
+ provide: CLIENT_TOKEN,
59
+ useExisting: token,
60
+ },
61
+ {
62
+ provide: common_1.Logger,
63
+ useValue: typeof opts.logger === 'string' ? new common_1.Logger(opts.logger) : opts.logger,
64
+ },
65
+ ];
66
+ return {
67
+ module: RedisCoreModule_1,
68
+ ...metadata,
69
+ providers: [
70
+ ...(metadata.providers ?? []),
71
+ ...providers,
54
72
  {
55
- provide: redis_constants_js_1.IOREDIS_MODULE_TOKEN,
73
+ provide: constants_js_1.IOREDIS_MODULE_TOKEN,
56
74
  useValue: crypto.randomUUID(),
57
75
  },
58
- connectionProvider,
59
76
  ],
60
- exports: [connectionProvider],
77
+ exports: [constants_js_1.IOREDIS_CONNECTION_OPTIONS, token, ...(metadata.exports ?? [])],
61
78
  };
62
79
  }
63
- static async _createClient(options) {
64
- if (options.host && options.nodes) {
65
- throw new TypeError(`You should set either "host" or "nodes", not both`);
80
+ static _readConnectionOptions(options, prefix = 'REDIS_') {
81
+ const env = node_process_1.default.env;
82
+ const out = (0, objects_1.clone)(options || {});
83
+ let redisOptions;
84
+ if ((0, utils_js_1.isClusterOptions)(out)) {
85
+ redisOptions = out.redisOptions = out.redisOptions || {};
86
+ out.nodes = out.nodes ?? (env[prefix + 'NODES'] || 'localhost:6379').split(/\s*,\s*/);
66
87
  }
88
+ else {
89
+ redisOptions = out;
90
+ out.host = out.host ?? env[prefix + 'HOST'] ?? 'localhost';
91
+ out.port = out.port ?? (0, putil_varhelpers_1.toIntDef)(env[prefix + 'PORT'], 6379);
92
+ }
93
+ redisOptions.db = redisOptions.db ?? (0, putil_varhelpers_1.toIntDef)(env[prefix + 'DB'], 0);
94
+ redisOptions.username = redisOptions.username ?? env[prefix + 'USERNAME'];
95
+ redisOptions.password = redisOptions.password ?? env[prefix + 'PASSWORD'];
96
+ redisOptions.autoResubscribe = redisOptions.autoResubscribe ?? (0, putil_varhelpers_1.toBoolean)(env[prefix + 'AUTO_RESUBSCRIBE']);
97
+ if (!redisOptions.reconnectOnError) {
98
+ let n = env[prefix + 'RECONNECT_ON_ERROR'];
99
+ if (n === 'true' || n === 'false') {
100
+ n = (0, putil_varhelpers_1.toBoolean)(n);
101
+ }
102
+ else
103
+ n = (0, putil_varhelpers_1.toInt)(n);
104
+ redisOptions.reconnectOnError = () => n;
105
+ }
106
+ redisOptions.connectTimeout = redisOptions.connectTimeout ?? (0, putil_varhelpers_1.toInt)(env[prefix + 'CONNECT_TIMEOUT']);
107
+ redisOptions.socketTimeout = redisOptions.socketTimeout ?? (0, putil_varhelpers_1.toInt)(env[prefix + 'SOCKET_TIMEOUT']);
108
+ redisOptions.keepAlive = redisOptions.keepAlive ?? (0, putil_varhelpers_1.toInt)(env[prefix + 'KEEP_ALIVE']);
109
+ redisOptions.noDelay = redisOptions.noDelay ?? (0, putil_varhelpers_1.toBoolean)(env[prefix + 'NO_DELAY']);
110
+ redisOptions.connectionName = redisOptions.connectionName ?? env[prefix + 'CONNECTION_NAME'];
111
+ redisOptions.maxRetriesPerRequest =
112
+ redisOptions.maxRetriesPerRequest ?? (0, putil_varhelpers_1.toInt)(env[prefix + 'MAX_RETRIES_PER_REQUEST']);
113
+ return out;
114
+ }
115
+ static _createClient(options) {
67
116
  const opts = { ...options };
68
- const isCluster = (0, utils_js_1.isClusterOptions)(opts);
69
117
  let client;
70
- if (isCluster) {
118
+ if ((0, utils_js_1.isClusterOptions)(opts)) {
71
119
  delete opts.name;
72
120
  delete opts.nodes;
121
+ opts.lazyConnect = true;
73
122
  const cluster = new ioredis_1.Cluster(opts.nodes, opts);
74
123
  client = new redis_client_js_1.RedisClient({ cluster });
75
124
  }
76
- else {
77
- if (options.host && options.host.includes('://')) {
78
- const url = new URL(options.host);
79
- options.host = url.hostname;
125
+ else if ((0, utils_js_1.isStandaloneOptions)(opts)) {
126
+ if (opts.host && opts.host.includes('://')) {
127
+ const url = new URL(opts.host);
128
+ opts.host = url.hostname;
80
129
  if (url.port)
81
- options.port = parseInt(url.port, 10);
130
+ opts.port = parseInt(url.port, 10);
82
131
  if (url.username)
83
- options.username = url.username;
132
+ opts.username = url.username;
84
133
  if (url.password)
85
- options.password = url.password;
134
+ opts.password = url.password;
86
135
  if (url.protocol === 'rediss:') {
87
136
  // @ts-ignore
88
- options.tls = true;
137
+ opts.tls = true;
89
138
  }
90
139
  const db = parseInt(url.pathname.substring(1), 10);
91
140
  if (db > 0)
92
- options.db = db;
141
+ opts.db = db;
93
142
  }
94
143
  const standalone = new ioredis_1.default({
95
- ...options,
144
+ ...opts,
96
145
  lazyConnect: true,
97
146
  });
98
147
  client = new redis_client_js_1.RedisClient({ standalone });
99
148
  }
149
+ else
150
+ throw new TypeError(`Invalid connection options`);
100
151
  return client;
101
152
  }
153
+ /**
154
+ *
155
+ * @constructor
156
+ */
157
+ constructor(client, connectionOptions, logger) {
158
+ this.client = client;
159
+ this.connectionOptions = connectionOptions;
160
+ this.logger = logger;
161
+ }
102
162
  async onApplicationBootstrap() {
103
- const opts = this.options;
104
- const logger = node_process_1.default.env.NODE_ENV === 'test' ? undefined : opts.logger;
163
+ const opts = this.connectionOptions;
105
164
  if (!opts.lazyConnect) {
106
165
  const isCluster = (0, utils_js_1.isClusterOptions)(opts);
107
166
  const hosts = isCluster
@@ -110,15 +169,14 @@ let RedisCoreModule = RedisCoreModule_1 = class RedisCoreModule {
110
169
  .join(', ')
111
170
  : opts.host;
112
171
  if (hosts) {
113
- logger?.log('Connecting to redis at ' + ansi_colors_1.default.blue(hosts));
172
+ this.logger?.log('Connecting to redis at ' + ansi_colors_1.default.blue(hosts));
114
173
  common_1.Logger.flush();
115
- const client = this.moduleRef.get(this.options.token || redis_client_js_1.RedisClient);
116
174
  try {
117
- await client.redis.connect();
118
- await client.redis.ping();
175
+ await this.client.redis.connect();
176
+ await this.client.redis.ping();
119
177
  }
120
178
  catch (e) {
121
- logger?.error('Redis connection failed: ' + e.message);
179
+ this.logger?.error('Redis connection failed: ' + e.message);
122
180
  throw e;
123
181
  }
124
182
  }
@@ -126,8 +184,7 @@ let RedisCoreModule = RedisCoreModule_1 = class RedisCoreModule {
126
184
  }
127
185
  async onApplicationShutdown() {
128
186
  try {
129
- const client = this.moduleRef.get(this.options.token || redis_client_js_1.RedisClient);
130
- await client.quit();
187
+ await this.client.quit();
131
188
  }
132
189
  catch {
133
190
  //
@@ -136,8 +193,7 @@ let RedisCoreModule = RedisCoreModule_1 = class RedisCoreModule {
136
193
  };
137
194
  exports.RedisCoreModule = RedisCoreModule;
138
195
  exports.RedisCoreModule = RedisCoreModule = RedisCoreModule_1 = tslib_1.__decorate([
139
- (0, common_1.Global)(),
140
- (0, common_1.Module)({}),
141
- tslib_1.__param(0, (0, common_1.Inject)(redis_constants_js_1.IOREDIS_MODULE_OPTIONS)),
142
- tslib_1.__metadata("design:paramtypes", [Object, core_1.ModuleRef])
196
+ tslib_1.__param(0, (0, common_1.Inject)(CLIENT_TOKEN)),
197
+ tslib_1.__param(1, (0, common_1.Inject)(constants_js_1.IOREDIS_CONNECTION_OPTIONS)),
198
+ tslib_1.__metadata("design:paramtypes", [redis_client_js_1.RedisClient, Object, common_1.Logger])
143
199
  ], RedisCoreModule);
package/cjs/utils.js CHANGED
@@ -1,6 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.isClusterOptions = isClusterOptions;
4
+ exports.isStandaloneOptions = isStandaloneOptions;
4
5
  function isClusterOptions(options) {
5
6
  return options && typeof options === 'object' && Array.isArray(options.nodes);
6
7
  }
8
+ function isStandaloneOptions(options) {
9
+ return options && typeof options === 'object' && options.host;
10
+ }
@@ -0,0 +1,2 @@
1
+ export const IOREDIS_CONNECTION_OPTIONS = Symbol('IOREDIS_CONNECTION_OPTIONS');
2
+ export const IOREDIS_MODULE_TOKEN = Symbol('IOREDIS_MODULE_ID');
package/esm/index.js CHANGED
@@ -1,4 +1,3 @@
1
- export * from './redis.interface.js';
2
1
  export * from './redis.module.js';
3
2
  export * from './redis-client.js';
4
3
  export * from './shared-lock.js';
@@ -1,104 +1,163 @@
1
1
  var RedisCoreModule_1;
2
2
  import { __decorate, __metadata, __param } from "tslib";
3
+ import assert from 'node:assert';
3
4
  import process from 'node:process';
4
- import { Global, Inject, Logger, Module, } from '@nestjs/common';
5
- import { ModuleRef } from '@nestjs/core';
5
+ import { clone } from '@jsopen/objects';
6
+ import { Inject, Logger } from '@nestjs/common';
6
7
  import colors from 'ansi-colors';
7
8
  import * as crypto from 'crypto';
8
9
  import Redis, { Cluster } from 'ioredis';
9
- import { IOREDIS_MODULE_OPTIONS, IOREDIS_MODULE_TOKEN } from './redis.constants.js';
10
+ import { toBoolean, toInt, toIntDef } from 'putil-varhelpers';
11
+ import { IOREDIS_CONNECTION_OPTIONS, IOREDIS_MODULE_TOKEN } from './constants.js';
10
12
  import { RedisClient } from './redis-client.js';
11
- import { isClusterOptions } from './utils.js';
13
+ import { isClusterOptions, isStandaloneOptions } from './utils.js';
14
+ const CLIENT_TOKEN = Symbol('CLIENT_TOKEN');
12
15
  let RedisCoreModule = RedisCoreModule_1 = class RedisCoreModule {
13
- constructor(options, moduleRef) {
14
- this.options = options;
15
- this.moduleRef = moduleRef;
16
- }
17
- static forRoot(options) {
18
- const optionsProvider = {
19
- provide: IOREDIS_MODULE_OPTIONS,
20
- useValue: options,
21
- };
22
- const token = options.token || RedisClient;
23
- const connectionProvider = {
24
- provide: token,
25
- useFactory: () => this._createClient(options),
26
- };
27
- return {
28
- module: RedisCoreModule_1,
29
- providers: [connectionProvider, optionsProvider],
30
- exports: [connectionProvider],
31
- };
16
+ static forRoot(moduleOptions) {
17
+ const connectionOptions = this._readConnectionOptions(moduleOptions.useValue, moduleOptions.envPrefix);
18
+ return this._createDynamicModule(moduleOptions, {
19
+ global: moduleOptions.global,
20
+ providers: [
21
+ {
22
+ provide: IOREDIS_CONNECTION_OPTIONS,
23
+ useValue: connectionOptions,
24
+ },
25
+ ],
26
+ });
32
27
  }
33
28
  static forRootAsync(asyncOptions) {
34
- if (!asyncOptions.useFactory)
35
- throw new Error('Invalid configuration. Must provide "useFactory"');
36
- const token = asyncOptions.token || RedisClient;
37
- const connectionProvider = {
38
- provide: token,
39
- inject: [IOREDIS_MODULE_OPTIONS],
40
- useFactory: async (moduleOptions) => this._createClient(moduleOptions),
41
- };
42
- return {
43
- module: RedisCoreModule_1,
44
- imports: asyncOptions.imports,
29
+ assert.ok(asyncOptions.useFactory, 'useFactory is required');
30
+ return this._createDynamicModule(asyncOptions, {
31
+ global: asyncOptions.global,
45
32
  providers: [
46
33
  {
47
- provide: IOREDIS_MODULE_OPTIONS,
48
- useFactory: asyncOptions.useFactory,
49
- inject: asyncOptions.inject || [],
34
+ provide: IOREDIS_CONNECTION_OPTIONS,
35
+ inject: asyncOptions.inject,
36
+ useFactory: async (...args) => {
37
+ const opts = await asyncOptions.useFactory(...args);
38
+ return this._readConnectionOptions(opts, asyncOptions.envPrefix);
39
+ },
50
40
  },
41
+ ],
42
+ });
43
+ }
44
+ static _createDynamicModule(opts, metadata) {
45
+ const token = opts.token ?? RedisClient;
46
+ const providers = [
47
+ {
48
+ provide: token,
49
+ inject: [IOREDIS_CONNECTION_OPTIONS],
50
+ useFactory: async (connectionOptions) => {
51
+ return this._createClient(connectionOptions);
52
+ },
53
+ },
54
+ {
55
+ provide: CLIENT_TOKEN,
56
+ useExisting: token,
57
+ },
58
+ {
59
+ provide: Logger,
60
+ useValue: typeof opts.logger === 'string' ? new Logger(opts.logger) : opts.logger,
61
+ },
62
+ ];
63
+ return {
64
+ module: RedisCoreModule_1,
65
+ ...metadata,
66
+ providers: [
67
+ ...(metadata.providers ?? []),
68
+ ...providers,
51
69
  {
52
70
  provide: IOREDIS_MODULE_TOKEN,
53
71
  useValue: crypto.randomUUID(),
54
72
  },
55
- connectionProvider,
56
73
  ],
57
- exports: [connectionProvider],
74
+ exports: [IOREDIS_CONNECTION_OPTIONS, token, ...(metadata.exports ?? [])],
58
75
  };
59
76
  }
60
- static async _createClient(options) {
61
- if (options.host && options.nodes) {
62
- throw new TypeError(`You should set either "host" or "nodes", not both`);
77
+ static _readConnectionOptions(options, prefix = 'REDIS_') {
78
+ const env = process.env;
79
+ const out = clone(options || {});
80
+ let redisOptions;
81
+ if (isClusterOptions(out)) {
82
+ redisOptions = out.redisOptions = out.redisOptions || {};
83
+ out.nodes = out.nodes ?? (env[prefix + 'NODES'] || 'localhost:6379').split(/\s*,\s*/);
63
84
  }
85
+ else {
86
+ redisOptions = out;
87
+ out.host = out.host ?? env[prefix + 'HOST'] ?? 'localhost';
88
+ out.port = out.port ?? toIntDef(env[prefix + 'PORT'], 6379);
89
+ }
90
+ redisOptions.db = redisOptions.db ?? toIntDef(env[prefix + 'DB'], 0);
91
+ redisOptions.username = redisOptions.username ?? env[prefix + 'USERNAME'];
92
+ redisOptions.password = redisOptions.password ?? env[prefix + 'PASSWORD'];
93
+ redisOptions.autoResubscribe = redisOptions.autoResubscribe ?? toBoolean(env[prefix + 'AUTO_RESUBSCRIBE']);
94
+ if (!redisOptions.reconnectOnError) {
95
+ let n = env[prefix + 'RECONNECT_ON_ERROR'];
96
+ if (n === 'true' || n === 'false') {
97
+ n = toBoolean(n);
98
+ }
99
+ else
100
+ n = toInt(n);
101
+ redisOptions.reconnectOnError = () => n;
102
+ }
103
+ redisOptions.connectTimeout = redisOptions.connectTimeout ?? toInt(env[prefix + 'CONNECT_TIMEOUT']);
104
+ redisOptions.socketTimeout = redisOptions.socketTimeout ?? toInt(env[prefix + 'SOCKET_TIMEOUT']);
105
+ redisOptions.keepAlive = redisOptions.keepAlive ?? toInt(env[prefix + 'KEEP_ALIVE']);
106
+ redisOptions.noDelay = redisOptions.noDelay ?? toBoolean(env[prefix + 'NO_DELAY']);
107
+ redisOptions.connectionName = redisOptions.connectionName ?? env[prefix + 'CONNECTION_NAME'];
108
+ redisOptions.maxRetriesPerRequest =
109
+ redisOptions.maxRetriesPerRequest ?? toInt(env[prefix + 'MAX_RETRIES_PER_REQUEST']);
110
+ return out;
111
+ }
112
+ static _createClient(options) {
64
113
  const opts = { ...options };
65
- const isCluster = isClusterOptions(opts);
66
114
  let client;
67
- if (isCluster) {
115
+ if (isClusterOptions(opts)) {
68
116
  delete opts.name;
69
117
  delete opts.nodes;
118
+ opts.lazyConnect = true;
70
119
  const cluster = new Cluster(opts.nodes, opts);
71
120
  client = new RedisClient({ cluster });
72
121
  }
73
- else {
74
- if (options.host && options.host.includes('://')) {
75
- const url = new URL(options.host);
76
- options.host = url.hostname;
122
+ else if (isStandaloneOptions(opts)) {
123
+ if (opts.host && opts.host.includes('://')) {
124
+ const url = new URL(opts.host);
125
+ opts.host = url.hostname;
77
126
  if (url.port)
78
- options.port = parseInt(url.port, 10);
127
+ opts.port = parseInt(url.port, 10);
79
128
  if (url.username)
80
- options.username = url.username;
129
+ opts.username = url.username;
81
130
  if (url.password)
82
- options.password = url.password;
131
+ opts.password = url.password;
83
132
  if (url.protocol === 'rediss:') {
84
133
  // @ts-ignore
85
- options.tls = true;
134
+ opts.tls = true;
86
135
  }
87
136
  const db = parseInt(url.pathname.substring(1), 10);
88
137
  if (db > 0)
89
- options.db = db;
138
+ opts.db = db;
90
139
  }
91
140
  const standalone = new Redis({
92
- ...options,
141
+ ...opts,
93
142
  lazyConnect: true,
94
143
  });
95
144
  client = new RedisClient({ standalone });
96
145
  }
146
+ else
147
+ throw new TypeError(`Invalid connection options`);
97
148
  return client;
98
149
  }
150
+ /**
151
+ *
152
+ * @constructor
153
+ */
154
+ constructor(client, connectionOptions, logger) {
155
+ this.client = client;
156
+ this.connectionOptions = connectionOptions;
157
+ this.logger = logger;
158
+ }
99
159
  async onApplicationBootstrap() {
100
- const opts = this.options;
101
- const logger = process.env.NODE_ENV === 'test' ? undefined : opts.logger;
160
+ const opts = this.connectionOptions;
102
161
  if (!opts.lazyConnect) {
103
162
  const isCluster = isClusterOptions(opts);
104
163
  const hosts = isCluster
@@ -107,15 +166,14 @@ let RedisCoreModule = RedisCoreModule_1 = class RedisCoreModule {
107
166
  .join(', ')
108
167
  : opts.host;
109
168
  if (hosts) {
110
- logger?.log('Connecting to redis at ' + colors.blue(hosts));
169
+ this.logger?.log('Connecting to redis at ' + colors.blue(hosts));
111
170
  Logger.flush();
112
- const client = this.moduleRef.get(this.options.token || RedisClient);
113
171
  try {
114
- await client.redis.connect();
115
- await client.redis.ping();
172
+ await this.client.redis.connect();
173
+ await this.client.redis.ping();
116
174
  }
117
175
  catch (e) {
118
- logger?.error('Redis connection failed: ' + e.message);
176
+ this.logger?.error('Redis connection failed: ' + e.message);
119
177
  throw e;
120
178
  }
121
179
  }
@@ -123,8 +181,7 @@ let RedisCoreModule = RedisCoreModule_1 = class RedisCoreModule {
123
181
  }
124
182
  async onApplicationShutdown() {
125
183
  try {
126
- const client = this.moduleRef.get(this.options.token || RedisClient);
127
- await client.quit();
184
+ await this.client.quit();
128
185
  }
129
186
  catch {
130
187
  //
@@ -132,9 +189,8 @@ let RedisCoreModule = RedisCoreModule_1 = class RedisCoreModule {
132
189
  }
133
190
  };
134
191
  RedisCoreModule = RedisCoreModule_1 = __decorate([
135
- Global(),
136
- Module({}),
137
- __param(0, Inject(IOREDIS_MODULE_OPTIONS)),
138
- __metadata("design:paramtypes", [Object, ModuleRef])
192
+ __param(0, Inject(CLIENT_TOKEN)),
193
+ __param(1, Inject(IOREDIS_CONNECTION_OPTIONS)),
194
+ __metadata("design:paramtypes", [RedisClient, Object, Logger])
139
195
  ], RedisCoreModule);
140
196
  export { RedisCoreModule };
package/esm/utils.js CHANGED
@@ -1,3 +1,6 @@
1
1
  export function isClusterOptions(options) {
2
2
  return options && typeof options === 'object' && Array.isArray(options.nodes);
3
3
  }
4
+ export function isStandaloneOptions(options) {
5
+ return options && typeof options === 'object' && options.host;
6
+ }
package/package.json CHANGED
@@ -1,11 +1,13 @@
1
1
  {
2
2
  "name": "@xnestjs/ioredis",
3
- "version": "1.2.5",
3
+ "version": "1.3.1",
4
4
  "description": "NestJS extension library for ioredis",
5
5
  "author": "Panates",
6
6
  "license": "MIT",
7
7
  "dependencies": {
8
+ "@jsopen/objects": "^1.5.2",
8
9
  "ansi-colors": "^4.1.3",
10
+ "putil-varhelpers": "^1.6.5",
9
11
  "tslib": "^2.8.1"
10
12
  },
11
13
  "peerDependencies": {
@@ -0,0 +1,2 @@
1
+ export declare const IOREDIS_CONNECTION_OPTIONS: unique symbol;
2
+ export declare const IOREDIS_MODULE_TOKEN: unique symbol;
package/types/index.d.cts CHANGED
@@ -1,4 +1,3 @@
1
- export * from './redis.interface.js';
2
1
  export * from './redis.module.js';
3
2
  export * from './redis-client.js';
4
3
  export * from './shared-lock.js';
package/types/index.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- export * from './redis.interface.js';
2
1
  export * from './redis.module.js';
3
2
  export * from './redis-client.js';
4
3
  export * from './shared-lock.js';
@@ -1,13 +1,20 @@
1
- import { DynamicModule, OnApplicationBootstrap, OnApplicationShutdown } from '@nestjs/common';
2
- import { ModuleRef } from '@nestjs/core';
3
- import { RedisClientAsyncOptions, RedisClientOptions, RedisClusterAsyncOptions, RedisClusterOptions } from './redis.interface.js';
1
+ import { DynamicModule, Logger, OnApplicationBootstrap, OnApplicationShutdown } from '@nestjs/common';
2
+ import { RedisClient } from './redis-client.js';
3
+ import type { RedisAsyncModuleOptions, RedisConnectionOptions, RedisModuleOptions } from './types.js';
4
4
  export declare class RedisCoreModule implements OnApplicationBootstrap, OnApplicationShutdown {
5
- private readonly options;
6
- private readonly moduleRef;
7
- constructor(options: RedisClientOptions | RedisClusterOptions, moduleRef: ModuleRef);
8
- static forRoot(options: RedisClientOptions | RedisClusterOptions): DynamicModule;
9
- static forRootAsync(asyncOptions: RedisClientAsyncOptions | RedisClusterAsyncOptions): DynamicModule;
5
+ protected client: RedisClient;
6
+ private readonly connectionOptions;
7
+ private logger?;
8
+ static forRoot(moduleOptions: RedisModuleOptions): DynamicModule;
9
+ static forRootAsync(asyncOptions: RedisAsyncModuleOptions): DynamicModule;
10
+ private static _createDynamicModule;
11
+ private static _readConnectionOptions;
10
12
  private static _createClient;
13
+ /**
14
+ *
15
+ * @constructor
16
+ */
17
+ constructor(client: RedisClient, connectionOptions: RedisConnectionOptions, logger?: Logger | undefined);
11
18
  onApplicationBootstrap(): Promise<void>;
12
19
  onApplicationShutdown(): Promise<void>;
13
20
  }
@@ -1,6 +1,6 @@
1
- import { DynamicModule } from '@nestjs/common';
2
- import { RedisClientAsyncOptions, RedisClientOptions, RedisClusterAsyncOptions, RedisClusterOptions } from './redis.interface.js';
1
+ import { type DynamicModule } from '@nestjs/common';
2
+ import type { RedisAsyncModuleOptions, RedisModuleOptions } from './types.js';
3
3
  export declare class RedisModule {
4
- static forRoot(options: RedisClientOptions | RedisClusterOptions): DynamicModule;
5
- static forRootAsync(options: RedisClientAsyncOptions | RedisClusterAsyncOptions): DynamicModule;
4
+ static forRoot(options: RedisModuleOptions): DynamicModule;
5
+ static forRootAsync(options: RedisAsyncModuleOptions): DynamicModule;
6
6
  }
package/types/types.d.ts CHANGED
@@ -1,4 +1,38 @@
1
+ import type { Logger } from '@nestjs/common';
2
+ import type { ModuleMetadata } from '@nestjs/common/interfaces';
3
+ import type { InjectionToken } from '@nestjs/common/interfaces/modules/injection-token.interface';
4
+ import type { RedisOptions } from 'ioredis';
5
+ import type { ClusterOptions } from 'ioredis/built/cluster/ClusterOptions';
6
+ import type { ClusterNode } from 'ioredis/built/cluster/index.js';
1
7
  import type { Lock } from 'redis-semaphore/lib/Lock';
8
+ export interface RedisStandaloneConnectionOptions extends RedisOptions {
9
+ }
10
+ export interface RedisClusterConnectionOptions extends ClusterOptions {
11
+ nodes: ClusterNode[];
12
+ }
13
+ export type RedisConnectionOptions = RedisStandaloneConnectionOptions | RedisClusterConnectionOptions;
14
+ export interface RedisStandaloneModuleOptions extends BaseRedisModuleOptions {
15
+ useValue?: RedisStandaloneConnectionOptions;
16
+ }
17
+ export interface RedisStandaloneAsyncModuleOptions extends BaseRedisModuleOptions, Pick<ModuleMetadata, 'imports'> {
18
+ inject?: InjectionToken[];
19
+ useFactory: (...args: any[]) => Promise<RedisStandaloneConnectionOptions> | RedisStandaloneConnectionOptions;
20
+ }
21
+ export interface RedisClusterModuleOptions extends BaseRedisModuleOptions {
22
+ useValue?: RedisClusterConnectionOptions;
23
+ }
24
+ export interface RedisClusterAsyncModuleOptions extends BaseRedisModuleOptions, Pick<ModuleMetadata, 'imports'> {
25
+ inject?: InjectionToken[];
26
+ useFactory: (...args: any[]) => Promise<RedisClusterConnectionOptions> | RedisClusterConnectionOptions;
27
+ }
28
+ export type RedisModuleOptions = RedisStandaloneModuleOptions | RedisClusterModuleOptions;
29
+ export type RedisAsyncModuleOptions = RedisStandaloneAsyncModuleOptions | RedisClusterAsyncModuleOptions;
30
+ export interface BaseRedisModuleOptions {
31
+ token?: InjectionToken;
32
+ envPrefix?: string;
33
+ logger?: Logger | string;
34
+ global?: boolean;
35
+ }
2
36
  export interface SharedLock extends Lock {
3
37
  readonly refCount: number;
4
38
  acquire(): Promise<void>;
package/types/utils.d.ts CHANGED
@@ -1,2 +1,3 @@
1
- import { RedisClusterOptions } from './redis.interface.js';
2
- export declare function isClusterOptions(options: any): options is RedisClusterOptions;
1
+ import { RedisClusterConnectionOptions, RedisStandaloneConnectionOptions } from './types.js';
2
+ export declare function isClusterOptions(options: any): options is RedisClusterConnectionOptions;
3
+ export declare function isStandaloneOptions(options: any): options is RedisStandaloneConnectionOptions;
@@ -1,5 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.IOREDIS_MODULE_TOKEN = exports.IOREDIS_MODULE_OPTIONS = void 0;
4
- exports.IOREDIS_MODULE_OPTIONS = Symbol('IOREDIS_MODULE_OPTIONS');
5
- exports.IOREDIS_MODULE_TOKEN = Symbol('IOREDIS_MODULE_ID');
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,2 +0,0 @@
1
- export const IOREDIS_MODULE_OPTIONS = Symbol('IOREDIS_MODULE_OPTIONS');
2
- export const IOREDIS_MODULE_TOKEN = Symbol('IOREDIS_MODULE_ID');
@@ -1 +0,0 @@
1
- export {};
@@ -1,2 +0,0 @@
1
- export declare const IOREDIS_MODULE_OPTIONS: unique symbol;
2
- export declare const IOREDIS_MODULE_TOKEN: unique symbol;
@@ -1,50 +0,0 @@
1
- import { Logger } from '@nestjs/common';
2
- import type { ModuleMetadata } from '@nestjs/common/interfaces';
3
- import type { RedisOptions } from 'ioredis';
4
- import type { ClusterOptions } from 'ioredis/built/cluster/ClusterOptions';
5
- import type { ClusterNode } from 'ioredis/built/cluster/index.js';
6
- export interface RedisClientOptions extends RedisOptions {
7
- /**
8
- * Injection token
9
- */
10
- token?: any;
11
- logger?: Logger;
12
- }
13
- export interface RedisClusterOptions extends ClusterOptions {
14
- /**
15
- * Injection token
16
- */
17
- token?: any;
18
- logger?: Logger;
19
- nodes: ClusterNode[];
20
- }
21
- export interface RedisClientAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
22
- /**
23
- * Injection token
24
- */
25
- token?: any;
26
- useFactory?: (...args: any[]) => Promise<RedisClientOptions> | RedisClientOptions;
27
- inject?: any[];
28
- }
29
- export interface RedisClusterAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
30
- /**
31
- * Injection token
32
- */
33
- token?: any;
34
- useFactory?: (...args: any[]) => Promise<RedisClusterOptions> | RedisClusterOptions;
35
- inject?: any[];
36
- }
37
- export interface LockSettings {
38
- /**
39
- * This parameter is only used if lock has been acquired without leaseTimeout parameter definition.
40
- * Lock expires after `lockWatchdogTimeout` if watchdog
41
- * didn't extend it to next `lockWatchdogTimeout` time interval.
42
- *
43
- * This prevents against infinity locked locks due to Redisson client crush or
44
- * any other reason when lock can't be released in proper way.
45
- *
46
- * - Unit: milliseconds
47
- * - Default: 30000 milliseconds
48
- */
49
- lockWatchdogTimeout?: bigint;
50
- }