diff --git a/README.md b/README.md index 86a8ebd..6dcdb01 100644 --- a/README.md +++ b/README.md @@ -1,209 +1,468 @@ - # Nestjs Redis -Redis component for NestJs. - +Redis component for NestJs with cluster support. ### Installation **Yarn** + ```bash -yarn add nestjs-redis +yarn add nestjs-redis-cluster ``` **NPM** + ```bash -npm install nestjs-redis --save +npm install nestjs-redis-cluster --save ``` ### Getting Started -Let's register the RedisModule in `app.module.ts` + +##### RedisModule + +Let's register the ClusterModule and RedisModule in `app.module.ts` ```typescript -import { Module } from '@nestjs/common' -import { RedisModule} from 'nestjs-redis' +import { Module } from '@nestjs/common'; +import { RedisModule } from 'nestjs-redis-cluster'; @Module({ - imports: [ - RedisModule.register(options) - ], + imports: [RedisModule.register(options)], }) export class AppModule {} ``` + With Async + ```typescript import { Module } from '@nestjs/common'; -import { RedisModule} from 'nestjs-redis' +import { RedisModule } from 'nestjs-redis-redis'; @Module({ - imports: [ - RedisModule.forRootAsync({ - useFactory: (configService: ConfigService) => configService.get('redis'), // or use async method - //useFactory: async (configService: ConfigService) => configService.get('redis'), - inject:[ConfigService] - }), - ], + imports: [ + RedisModule.forRootAsync({ + useFactory: (configService: ConfigService) => configService.get('redis'), + // or use async method + //useFactory: async (configService: ConfigService) => configService.get('redis'), + inject: [ConfigService], + }), + ], }) export class AppModule {} ``` -And the config file look like this -With single client + +Config looks like this for RedisModule with single client + ```typescript export default { + host: process.env.REDIS_HOST, + port: parseInt(process.env.REDIS_PORT), + db: parseInt(process.env.REDIS_DB), + password: process.env.REDIS_PASSWORD, + keyPrefix: process.env.REDIS_PRIFIX, +}; +Or; +export default { + url: 'redis://:authpassword@127.0.0.1:6380/4', +}; +``` + +With custom error handler + +```typescript +export default { + url: 'redis://:authpassword@127.0.0.1:6380/4', + onClientReady: client => { + client.on('error', err => {}); + }, +}; +``` + +With multi client + +```typescript +export default [ + { + name: 'test1', + url: 'redis://:authpassword@127.0.0.1:6380/4', + }, + { + name: 'test2', host: process.env.REDIS_HOST, port: parseInt(process.env.REDIS_PORT), db: parseInt(process.env.REDIS_DB), password: process.env.REDIS_PASSWORD, keyPrefix: process.env.REDIS_PRIFIX, + }, +]; +``` + +And use in your service + +```typescript +import { Injectable } from '@nestjs/common'; +import { RedisService } from 'nestjs-redis-cluster'; + +@Injectable() +export class TestService { + constructor(private readonly redisService: RedisService) {} + async root(): Promise { + const client = await this.redisService.getClient('test'); + return true; + } } -Or -export default { - url: 'redis://:authpassword@127.0.0.1:6380/4', +``` + +Options + +```typescript +interface RedisOptions { + /** + * client name. default is a uuid, unique. + */ + name?: string; + url?: string; + port?: number; + host?: string; + /** + * 4 (IPv4) or 6 (IPv6), Defaults to 4. + */ + family?: number; + /** + * Local domain socket path. If set the port, host and family will be ignored. + */ + path?: string; + /** + * TCP KeepAlive on the socket with a X ms delay before start. Set to a non-number value to disable keepAlive. + */ + keepAlive?: number; + connectionName?: string; + /** + * If set, client will send AUTH command with the value of this option when connected. + */ + password?: string; + /** + * Database index to use. + */ + db?: number; + /** + * When a connection is established to the Redis server, the server might still be loading + * the database from disk. While loading, the server not respond to any commands. + * To work around this, when this option is true, ioredis will check the status of the Redis server, + * and when the Redis server is able to process commands, a ready event will be emitted. + */ + enableReadyCheck?: boolean; + keyPrefix?: string; + /** + * When the return value isn't a number, ioredis will stop trying to reconnect. + * Fixed in: https://github.com/DefinitelyTyped/DefinitelyTyped/pull/15858 + */ + retryStrategy?(times: number): number | false; + /** + * By default, all pending commands will be flushed with an error every + * 20 retry attempts. That makes sure commands won't wait forever when + * the connection is down. You can change this behavior by setting + * `maxRetriesPerRequest`. + * + * Set maxRetriesPerRequest to `null` to disable this behavior, and + * every command will wait forever until the connection is alive again + * (which is the default behavior before ioredis v4). + */ + maxRetriesPerRequest?: number | null; + /** + * 1/true means reconnect, 2 means reconnect and resend failed command. Returning false will ignore + * the error and do nothing. + */ + reconnectOnError?(error: Error): boolean | 1 | 2; + /** + * By default, if there is no active connection to the Redis server, commands are added to a queue + * and are executed once the connection is "ready" (when enableReadyCheck is true, "ready" means + * the Redis server has loaded the database from disk, otherwise means the connection to the Redis + * server has been established). If this option is false, when execute the command when the connection + * isn't ready, an error will be returned. + */ + enableOfflineQueue?: boolean; + /** + * The milliseconds before a timeout occurs during the initial connection to the Redis server. + * default: 10000. + */ + connectTimeout?: number; + /** + * After reconnected, if the previous connection was in the subscriber mode, client will auto re-subscribe these channels. + * default: true. + */ + autoResubscribe?: boolean; + /** + * If true, client will resend unfulfilled commands(e.g. block commands) in the previous connection when reconnected. + * default: true. + */ + autoResendUnfulfilledCommands?: boolean; + lazyConnect?: boolean; + tls?: tls.ConnectionOptions; + sentinels?: Array<{ host: string; port: number }>; + name?: string; + /** + * Enable READONLY mode for the connection. Only available for cluster mode. + * default: false. + */ + readOnly?: boolean; + /** + * If you are using the hiredis parser, it's highly recommended to enable this option. + * Create another instance with dropBufferSupport disabled for other commands that you want to return binary instead of string + */ + dropBufferSupport?: boolean; + /** + * Whether to show a friendly error stack. Will decrease the performance significantly. + */ + showFriendlyErrorStack?: boolean; } ``` + +##### ClusterModule + +Let's register the RedisClusterModule in `app.module.ts` + +```typescript +import { Module } from '@nestjs/common'; +import { RedisClusterModule } from 'nestjs-redis-cluster'; + +@Module({ + imports: [RedisClusterModule.register(options)], +}) +export class AppModule {} +``` + +With Async + +```typescript +import { Module } from '@nestjs/common'; +import { ClusterModule } from 'nestjs-redis-cluster'; + +@Module({ + imports: [ + RedisClusterModule.forRootAsync({ + useFactory: (configService: ConfigService) => + configService.get('cluster'), + // or use async method + //useFactory: async (configService: ConfigService) => configService.get('cluster'), + inject: [ConfigService], + }), + ], +}) +export class AppModule {} +``` + +Config looks like this for RedisClusterModule + +```typescript +export default { + nodes: [ + { + host: 6380, + port: '127.0.0.1', + }, + { + host: 6381, + port: '127.0.0.1', + }, + ], +}; +Or; +export default { + nodes: [ + { + url: 'redis://:authpassword@127.0.0.1:6380/4', + }, + { + url: 'redis://:authpassword@127.0.0.1:6381/4', + }, + ], +}; +``` + With custom error handler + ```typescript export default { - url: 'redis://:authpassword@127.0.0.1:6380/4', - onClientReady: (client) => { - client.on('error', (err) => {} - )}, -} + nodes: [ + { + url: 'redis://:authpassword@127.0.0.1:6380/4', + }, + ], + onClusterReady: cluster => { + cluster.on('error', err => {}); + }, +}; ``` -With multi client + +With multiple clusters + ```typescript export default [ - { - name:'test1', + { + name: 'test1', + nodes: [ + { url: 'redis://:authpassword@127.0.0.1:6380/4', - }, - { - name:'test2', + url: 'redis://:authpassword@127.0.0.1:6380/5', + }, + ], + }, + { + name: 'test2', + nodes: [ + { host: process.env.REDIS_HOST, port: parseInt(process.env.REDIS_PORT), db: parseInt(process.env.REDIS_DB), password: process.env.REDIS_PASSWORD, keyPrefix: process.env.REDIS_PRIFIX, - }, -] + }, + ], + }, +]; ``` + And use in your service + ```typescript import { Injectable } from '@nestjs/common'; -import { RedisService } from 'nestjs-redis'; +import { RedisClusterService } from 'nestjs-redis-cluster'; @Injectable() export class TestService { - constructor( - private readonly redisService: RedisService, - ) { } + constructor(private readonly redisService: RedisClusterService) {} async root(): Promise { - const client = await this.redisService.getClient('test') - return true + const client = await this.redisService.getCluster('test'); + return true; } } ``` + Options +Almost all the options are passed to ioredis/cluster (https://github.com/luin/ioredis/blob/master/lib/cluster/ClusterOptions.ts) + +The only additional options are `name`, `nodes`, and `onClusterReady`. + ```typescript -interface RedisOptions { - /** - * client name. default is a uuid, unique. - */ - name?: string; - url?: string; - port?: number; - host?: string; - /** - * 4 (IPv4) or 6 (IPv6), Defaults to 4. - */ - family?: number; - /** - * Local domain socket path. If set the port, host and family will be ignored. - */ - path?: string; - /** - * TCP KeepAlive on the socket with a X ms delay before start. Set to a non-number value to disable keepAlive. - */ - keepAlive?: number; - connectionName?: string; - /** - * If set, client will send AUTH command with the value of this option when connected. - */ - password?: string; - /** - * Database index to use. - */ - db?: number; - /** - * When a connection is established to the Redis server, the server might still be loading - * the database from disk. While loading, the server not respond to any commands. - * To work around this, when this option is true, ioredis will check the status of the Redis server, - * and when the Redis server is able to process commands, a ready event will be emitted. - */ - enableReadyCheck?: boolean; - keyPrefix?: string; - /** - * When the return value isn't a number, ioredis will stop trying to reconnect. - * Fixed in: https://github.com/DefinitelyTyped/DefinitelyTyped/pull/15858 - */ - retryStrategy?(times: number): number | false; - /** - * By default, all pending commands will be flushed with an error every - * 20 retry attempts. That makes sure commands won't wait forever when - * the connection is down. You can change this behavior by setting - * `maxRetriesPerRequest`. - * - * Set maxRetriesPerRequest to `null` to disable this behavior, and - * every command will wait forever until the connection is alive again - * (which is the default behavior before ioredis v4). - */ - maxRetriesPerRequest?: number | null; - /** - * 1/true means reconnect, 2 means reconnect and resend failed command. Returning false will ignore - * the error and do nothing. - */ - reconnectOnError?(error: Error): boolean | 1 | 2; - /** - * By default, if there is no active connection to the Redis server, commands are added to a queue - * and are executed once the connection is "ready" (when enableReadyCheck is true, "ready" means - * the Redis server has loaded the database from disk, otherwise means the connection to the Redis - * server has been established). If this option is false, when execute the command when the connection - * isn't ready, an error will be returned. - */ - enableOfflineQueue?: boolean; - /** - * The milliseconds before a timeout occurs during the initial connection to the Redis server. - * default: 10000. - */ - connectTimeout?: number; - /** - * After reconnected, if the previous connection was in the subscriber mode, client will auto re-subscribe these channels. - * default: true. - */ - autoResubscribe?: boolean; - /** - * If true, client will resend unfulfilled commands(e.g. block commands) in the previous connection when reconnected. - * default: true. - */ - autoResendUnfulfilledCommands?: boolean; - lazyConnect?: boolean; - tls?: tls.ConnectionOptions; - sentinels?: Array<{ host: string; port: number; }>; - name?: string; - /** - * Enable READONLY mode for the connection. Only available for cluster mode. - * default: false. - */ - readOnly?: boolean; - /** - * If you are using the hiredis parser, it's highly recommended to enable this option. - * Create another instance with dropBufferSupport disabled for other commands that you want to return binary instead of string - */ - dropBufferSupport?: boolean; +interface RedisClusterOptions { + /** + * client name. default is a uuid, unique. + */ + name?: string; + nodes: (string | number | { url: string?, host?: string, port?: number )[]; + /** - * Whether to show a friendly error stack. Will decrease the performance significantly. - */ - showFriendlyErrorStack?: boolean; + * See "Quick Start" section. + * + * @default (times) => Math.min(100 + times * 2, 2000) + */ + clusterRetryStrategy?: ( + times: number, + reason?: Error + ) => number | void | null; + + /** + * See Redis class. + * + * @default true + */ + enableOfflineQueue?: boolean; + + /** + * When enabled, ioredis only emits "ready" event when `CLUSTER INFO` + * command reporting the cluster is ready for handling commands. + * + * @default true + */ + enableReadyCheck?: boolean; + + /** + * Scale reads to the node with the specified role. + * + * @default "master" + */ + scaleReads?: NodeRole | Function; + + /** + * When a MOVED or ASK error is received, client will redirect the + * command to another node. + * This option limits the max redirections allowed to send a command. + * + * @default 16 + */ + maxRedirections?: number; + + /** + * When an error is received when sending a command (e.g. + * "Connection is closed." when the target Redis node is down), client will retry + * if `retryDelayOnFailover` is valid delay time (in ms). + * + * @default 100 + */ + retryDelayOnFailover?: number; + + /** + * When a CLUSTERDOWN error is received, client will retry + * if `retryDelayOnClusterDown` is valid delay time (in ms). + * + * @default 100 + */ + retryDelayOnClusterDown?: number; + + /** + * When a TRYAGAIN error is received, client will retry + * if `retryDelayOnTryAgain` is valid delay time (in ms). + * + * @default 100 + */ + retryDelayOnTryAgain?: number; + + /** + * The milliseconds before a timeout occurs while refreshing + * slots from the cluster. + * + * @default 1000 + */ + slotsRefreshTimeout?: number; + + /** + * The milliseconds between every automatic slots refresh. + * + * @default 5000 + */ + slotsRefreshInterval?: number; + + /** + * Passed to the constructor of `Redis` + * + * @default null + */ + redisOptions?: any; + + /** + * By default, When a new Cluster instance is created, + * it will connect to the Redis cluster automatically. + * If you want to keep the instance disconnected until the first command is called, + * set this option to `true`. + * + * @default false + */ + lazyConnect?: boolean; + + /** + * Hostnames will be resolved to IP addresses via this function. + * This is needed when the addresses of startup nodes are hostnames instead + * of IPs. + * + * You may provide a custom `lookup` function when you want to customize + * the cache behavior of the default function. + * + * @default require('dns').lookup + */ + dnsLookup?: DNSLookupFunction; + natMap?: INatMap; } ``` + That's it! diff --git a/dist/cluster-core.module.d.ts b/dist/cluster-core.module.d.ts new file mode 100644 index 0000000..dd32474 --- /dev/null +++ b/dist/cluster-core.module.d.ts @@ -0,0 +1,11 @@ +import { DynamicModule, OnModuleDestroy } from '@nestjs/common'; +import { ModuleRef } from '@nestjs/core'; +import { RedisClusterModuleAsyncOptions, RedisClusterModuleOptions } from './cluster.interface'; +export declare class ClusterCoreModule implements OnModuleDestroy { + private readonly options; + private readonly moduleRef; + constructor(options: RedisClusterModuleOptions | RedisClusterModuleOptions[], moduleRef: ModuleRef); + static register(options: RedisClusterModuleOptions | RedisClusterModuleOptions[]): DynamicModule; + static forRootAsync(options: RedisClusterModuleAsyncOptions): DynamicModule; + onModuleDestroy(): void; +} diff --git a/dist/cluster-core.module.js b/dist/cluster-core.module.js new file mode 100644 index 0000000..08dcb5c --- /dev/null +++ b/dist/cluster-core.module.js @@ -0,0 +1,72 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var __param = (this && this.__param) || function (paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } +}; +var ClusterCoreModule_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ClusterCoreModule = void 0; +const common_1 = require("@nestjs/common"); +const core_1 = require("@nestjs/core"); +const cluster_provider_1 = require("./cluster.provider"); +const cluster_constants_1 = require("./cluster.constants"); +const cluster_service_1 = require("./cluster.service"); +let ClusterCoreModule = ClusterCoreModule_1 = class ClusterCoreModule { + constructor(options, moduleRef) { + this.options = options; + this.moduleRef = moduleRef; + } + static register(options) { + return { + module: ClusterCoreModule_1, + providers: [ + cluster_provider_1.createCluster(), + { provide: cluster_constants_1.REDIS_CLUSTER_MODULE_OPTIONS, useValue: options }, + ], + exports: [cluster_service_1.RedisClusterService], + }; + } + static forRootAsync(options) { + return { + module: ClusterCoreModule_1, + imports: options.imports, + providers: [cluster_provider_1.createCluster(), cluster_provider_1.createAsyncClusterOptions(options)], + exports: [cluster_service_1.RedisClusterService], + }; + } + onModuleDestroy() { + const closeConnection = ({ clusters, defaultKey, }) => options => { + const name = options.name || defaultKey; + const cluster = clusters.get(name); + if (cluster && !options.keepAlive) { + cluster.disconnect(); + } + }; + const provider = this.moduleRef.get(cluster_constants_1.REDIS_CLUSTER); + const closeClusterConnection = closeConnection(provider); + if (Array.isArray(this.options)) { + this.options.forEach(closeClusterConnection); + } + else { + closeClusterConnection(this.options); + } + } +}; +ClusterCoreModule = ClusterCoreModule_1 = __decorate([ + common_1.Global(), + common_1.Module({ + providers: [cluster_service_1.RedisClusterService], + exports: [cluster_service_1.RedisClusterService], + }), + __param(0, common_1.Inject(cluster_constants_1.REDIS_CLUSTER_MODULE_OPTIONS)), + __metadata("design:paramtypes", [Object, core_1.ModuleRef]) +], ClusterCoreModule); +exports.ClusterCoreModule = ClusterCoreModule; diff --git a/dist/cluster.constants.d.ts b/dist/cluster.constants.d.ts new file mode 100644 index 0000000..258c021 --- /dev/null +++ b/dist/cluster.constants.d.ts @@ -0,0 +1,2 @@ +export declare const REDIS_CLUSTER_MODULE_OPTIONS: unique symbol; +export declare const REDIS_CLUSTER: unique symbol; diff --git a/dist/cluster.constants.js b/dist/cluster.constants.js new file mode 100644 index 0000000..4d1742f --- /dev/null +++ b/dist/cluster.constants.js @@ -0,0 +1,5 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.REDIS_CLUSTER = exports.REDIS_CLUSTER_MODULE_OPTIONS = void 0; +exports.REDIS_CLUSTER_MODULE_OPTIONS = Symbol('REDIS_CLUSTER_MODULE_OPTIONS'); +exports.REDIS_CLUSTER = Symbol('REDIS_CLUSTER'); diff --git a/dist/cluster.interface.d.ts b/dist/cluster.interface.d.ts new file mode 100644 index 0000000..bbc648f --- /dev/null +++ b/dist/cluster.interface.d.ts @@ -0,0 +1,11 @@ +import { ModuleMetadata } from '@nestjs/common/interfaces'; +import { Redis, ClusterOptions } from 'ioredis'; +export interface RedisClusterModuleOptions extends ClusterOptions { + name?: string; + nodes: (string | number | object)[]; + onClusterReady?(cluster: Redis): Promise; +} +export interface RedisClusterModuleAsyncOptions extends Pick { + useFactory?: (...args: any[]) => RedisClusterModuleOptions | RedisClusterModuleOptions[] | Promise | Promise; + inject?: any[]; +} diff --git a/dist/cluster.interface.js b/dist/cluster.interface.js new file mode 100644 index 0000000..c8ad2e5 --- /dev/null +++ b/dist/cluster.interface.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/dist/cluster.module.d.ts b/dist/cluster.module.d.ts new file mode 100644 index 0000000..dbeec69 --- /dev/null +++ b/dist/cluster.module.d.ts @@ -0,0 +1,6 @@ +import { DynamicModule } from '@nestjs/common'; +import { RedisClusterModuleAsyncOptions, RedisClusterModuleOptions } from './cluster.interface'; +export declare class RedisClusterModule { + static register(options: RedisClusterModuleOptions | RedisClusterModuleOptions[]): DynamicModule; + static forRootAsync(options: RedisClusterModuleAsyncOptions): DynamicModule; +} diff --git a/dist/cluster.module.js b/dist/cluster.module.js new file mode 100644 index 0000000..2ac89ae --- /dev/null +++ b/dist/cluster.module.js @@ -0,0 +1,30 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var RedisClusterModule_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.RedisClusterModule = void 0; +const common_1 = require("@nestjs/common"); +const cluster_core_module_1 = require("./cluster-core.module"); +let RedisClusterModule = RedisClusterModule_1 = class RedisClusterModule { + static register(options) { + return { + module: RedisClusterModule_1, + imports: [cluster_core_module_1.ClusterCoreModule.register(options)], + }; + } + static forRootAsync(options) { + return { + module: RedisClusterModule_1, + imports: [cluster_core_module_1.ClusterCoreModule.forRootAsync(options)], + }; + } +}; +RedisClusterModule = RedisClusterModule_1 = __decorate([ + common_1.Module({}) +], RedisClusterModule); +exports.RedisClusterModule = RedisClusterModule; diff --git a/dist/cluster.provider.d.ts b/dist/cluster.provider.d.ts new file mode 100644 index 0000000..46a31a7 --- /dev/null +++ b/dist/cluster.provider.d.ts @@ -0,0 +1,16 @@ +import { Redis } from 'ioredis'; +import { Provider } from '@nestjs/common'; +import { RedisClusterModuleAsyncOptions, RedisClusterModuleOptions } from './cluster.interface'; +export declare class RedisClusterError extends Error { +} +export interface RedisClusterProvider { + defaultKey: string; + clusters: Map; + size: number; +} +export declare const createCluster: () => Provider; +export declare const createAsyncClusterOptions: (options: RedisClusterModuleAsyncOptions) => { + provide: symbol; + useFactory: (...args: any[]) => RedisClusterModuleOptions | Promise | RedisClusterModuleOptions[] | Promise; + inject: any[]; +}; diff --git a/dist/cluster.provider.js b/dist/cluster.provider.js new file mode 100644 index 0000000..7b0c4b0 --- /dev/null +++ b/dist/cluster.provider.js @@ -0,0 +1,72 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __rest = (this && this.__rest) || function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) + t[p[i]] = s[p[i]]; + } + return t; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.createAsyncClusterOptions = exports.createCluster = exports.RedisClusterError = void 0; +const ioredis_1 = require("ioredis"); +const uuid = require("uuid"); +const cluster_constants_1 = require("./cluster.constants"); +class RedisClusterError extends Error { +} +exports.RedisClusterError = RedisClusterError; +function getCluster(options) { + return __awaiter(this, void 0, void 0, function* () { + const { onClusterReady, nodes } = options, opt = __rest(options, ["onClusterReady", "nodes"]); + const cluster = new ioredis_1.Cluster(nodes, opt); + if (onClusterReady) { + onClusterReady(cluster); + } + return cluster; + }); +} +exports.createCluster = () => ({ + provide: cluster_constants_1.REDIS_CLUSTER, + useFactory: (options) => __awaiter(void 0, void 0, void 0, function* () { + const clusters = new Map(); + let defaultKey = uuid(); + if (Array.isArray(options)) { + yield Promise.all(options.map((o) => __awaiter(void 0, void 0, void 0, function* () { + const key = o.name || defaultKey; + if (clusters.has(key)) { + throw new RedisClusterError(`${o.name || 'default'} cluster already exists`); + } + clusters.set(key, yield getCluster(o)); + }))); + } + else { + if (options.name && options.name.length !== 0) { + defaultKey = options.name; + } + clusters.set(defaultKey, yield getCluster(options)); + } + return { + defaultKey, + clusters, + size: clusters.size, + }; + }), + inject: [cluster_constants_1.REDIS_CLUSTER_MODULE_OPTIONS], +}); +exports.createAsyncClusterOptions = (options) => ({ + provide: cluster_constants_1.REDIS_CLUSTER_MODULE_OPTIONS, + useFactory: options.useFactory, + inject: options.inject, +}); diff --git a/dist/cluster.service.d.ts b/dist/cluster.service.d.ts new file mode 100644 index 0000000..df634e4 --- /dev/null +++ b/dist/cluster.service.d.ts @@ -0,0 +1,8 @@ +import { Redis } from 'ioredis'; +import { RedisClusterProvider } from './cluster.provider'; +export declare class RedisClusterService { + private readonly provider; + constructor(provider: RedisClusterProvider); + getCluster(name?: string): Redis; + getClusters(): Map; +} diff --git a/dist/cluster.service.js b/dist/cluster.service.js new file mode 100644 index 0000000..91719df --- /dev/null +++ b/dist/cluster.service.js @@ -0,0 +1,41 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var __param = (this && this.__param) || function (paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.RedisClusterService = void 0; +const common_1 = require("@nestjs/common"); +const cluster_constants_1 = require("./cluster.constants"); +const cluster_provider_1 = require("./cluster.provider"); +let RedisClusterService = class RedisClusterService { + constructor(provider) { + this.provider = provider; + } + getCluster(name) { + if (!name) { + name = this.provider.defaultKey; + } + if (!this.provider.clusters.has(name)) { + throw new cluster_provider_1.RedisClusterError(`cluster ${name} does not exist`); + } + return this.provider.clusters.get(name); + } + getClusters() { + return this.provider.clusters; + } +}; +RedisClusterService = __decorate([ + common_1.Injectable(), + __param(0, common_1.Inject(cluster_constants_1.REDIS_CLUSTER)), + __metadata("design:paramtypes", [Object]) +], RedisClusterService); +exports.RedisClusterService = RedisClusterService; diff --git a/dist/index.d.ts b/dist/index.d.ts index 3aae4f1..8c026e3 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -1,3 +1,6 @@ export * from './redis.service'; export * from './redis.module'; export * from './redis.interface'; +export * from './cluster.service'; +export * from './cluster.module'; +export * from './cluster.interface'; diff --git a/dist/index.js b/dist/index.js index 7759545..99be788 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1,7 +1,18 @@ "use strict"; -function __export(m) { - for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; -} +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __exportStar = (this && this.__exportStar) || function(m, exports) { + for (var p in m) if (p !== "default" && !exports.hasOwnProperty(p)) __createBinding(exports, m, p); +}; Object.defineProperty(exports, "__esModule", { value: true }); -__export(require("./redis.service")); -__export(require("./redis.module")); +__exportStar(require("./redis.service"), exports); +__exportStar(require("./redis.module"), exports); +__exportStar(require("./redis.interface"), exports); +__exportStar(require("./cluster.service"), exports); +__exportStar(require("./cluster.module"), exports); +__exportStar(require("./cluster.interface"), exports); diff --git a/dist/redis-core.module.js b/dist/redis-core.module.js index dc05008..714e4b6 100644 --- a/dist/redis-core.module.js +++ b/dist/redis-core.module.js @@ -11,11 +11,12 @@ var __metadata = (this && this.__metadata) || function (k, v) { var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; -Object.defineProperty(exports, "__esModule", { value: true }); var RedisCoreModule_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.RedisCoreModule = void 0; const common_1 = require("@nestjs/common"); const core_1 = require("@nestjs/core"); -const redis_client_provider_1 = require("./redis-client.provider"); +const redis_provider_1 = require("./redis.provider"); const redis_constants_1 = require("./redis.constants"); const redis_service_1 = require("./redis.service"); let RedisCoreModule = RedisCoreModule_1 = class RedisCoreModule { @@ -27,7 +28,7 @@ let RedisCoreModule = RedisCoreModule_1 = class RedisCoreModule { return { module: RedisCoreModule_1, providers: [ - redis_client_provider_1.createClient(), + redis_provider_1.createClient(), { provide: redis_constants_1.REDIS_MODULE_OPTIONS, useValue: options }, ], exports: [redis_service_1.RedisService], @@ -37,7 +38,7 @@ let RedisCoreModule = RedisCoreModule_1 = class RedisCoreModule { return { module: RedisCoreModule_1, imports: options.imports, - providers: [redis_client_provider_1.createClient(), redis_client_provider_1.createAsyncClientOptions(options)], + providers: [redis_provider_1.createClient(), redis_provider_1.createAsyncClientOptions(options)], exports: [redis_service_1.RedisService], }; } diff --git a/dist/redis.constants.js b/dist/redis.constants.js index bc59a1e..cbc6ad9 100644 --- a/dist/redis.constants.js +++ b/dist/redis.constants.js @@ -1,4 +1,5 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); +exports.REDIS_CLIENT = exports.REDIS_MODULE_OPTIONS = void 0; exports.REDIS_MODULE_OPTIONS = Symbol('REDIS_MODULE_OPTIONS'); exports.REDIS_CLIENT = Symbol('REDIS_CLIENT'); diff --git a/dist/redis.module.js b/dist/redis.module.js index 82ae8ca..8f2e37d 100644 --- a/dist/redis.module.js +++ b/dist/redis.module.js @@ -5,8 +5,9 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key, else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; -Object.defineProperty(exports, "__esModule", { value: true }); var RedisModule_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.RedisModule = void 0; const common_1 = require("@nestjs/common"); const redis_core_module_1 = require("./redis-core.module"); let RedisModule = RedisModule_1 = class RedisModule { diff --git a/dist/redis-client.provider.d.ts b/dist/redis.provider.d.ts similarity index 91% rename from dist/redis-client.provider.d.ts rename to dist/redis.provider.d.ts index 635d13b..731646b 100644 --- a/dist/redis-client.provider.d.ts +++ b/dist/redis.provider.d.ts @@ -8,7 +8,7 @@ export interface RedisClient { clients: Map; size: number; } -export declare const createClient: () => Provider; +export declare const createClient: () => Provider; export declare const createAsyncClientOptions: (options: RedisModuleAsyncOptions) => { provide: symbol; useFactory: (...args: any[]) => RedisModuleOptions | Promise | RedisModuleOptions[] | Promise; diff --git a/dist/redis-client.provider.js b/dist/redis.provider.js similarity index 80% rename from dist/redis-client.provider.js rename to dist/redis.provider.js index 448493f..4b94a5c 100644 --- a/dist/redis-client.provider.js +++ b/dist/redis.provider.js @@ -1,9 +1,10 @@ "use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; @@ -12,11 +13,14 @@ var __rest = (this && this.__rest) || function (s, e) { for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") - for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) if (e.indexOf(p[i]) < 0) - t[p[i]] = s[p[i]]; + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) + t[p[i]] = s[p[i]]; + } return t; }; Object.defineProperty(exports, "__esModule", { value: true }); +exports.createAsyncClientOptions = exports.createClient = exports.RedisClientError = void 0; const Redis = require("ioredis"); const uuid = require("uuid"); const redis_constants_1 = require("./redis.constants"); @@ -35,11 +39,11 @@ function getClient(options) { } exports.createClient = () => ({ provide: redis_constants_1.REDIS_CLIENT, - useFactory: (options) => __awaiter(this, void 0, void 0, function* () { + useFactory: (options) => __awaiter(void 0, void 0, void 0, function* () { const clients = new Map(); let defaultKey = uuid(); if (Array.isArray(options)) { - yield Promise.all(options.map((o) => __awaiter(this, void 0, void 0, function* () { + yield Promise.all(options.map((o) => __awaiter(void 0, void 0, void 0, function* () { const key = o.name || defaultKey; if (clients.has(key)) { throw new RedisClientError(`${o.name || 'default'} client is exists`); diff --git a/dist/redis.service.d.ts b/dist/redis.service.d.ts index 5deed75..0c986c8 100644 --- a/dist/redis.service.d.ts +++ b/dist/redis.service.d.ts @@ -1,5 +1,5 @@ import * as Redis from 'ioredis'; -import { RedisClient } from './redis-client.provider'; +import { RedisClient } from './redis.provider'; export declare class RedisService { private readonly redisClient; constructor(redisClient: RedisClient); diff --git a/dist/redis.service.js b/dist/redis.service.js index ef536c7..02b844c 100644 --- a/dist/redis.service.js +++ b/dist/redis.service.js @@ -12,9 +12,10 @@ var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; Object.defineProperty(exports, "__esModule", { value: true }); +exports.RedisService = void 0; const common_1 = require("@nestjs/common"); const redis_constants_1 = require("./redis.constants"); -const redis_client_provider_1 = require("./redis-client.provider"); +const redis_provider_1 = require("./redis.provider"); let RedisService = class RedisService { constructor(redisClient) { this.redisClient = redisClient; @@ -24,7 +25,7 @@ let RedisService = class RedisService { name = this.redisClient.defaultKey; } if (!this.redisClient.clients.has(name)) { - throw new redis_client_provider_1.RedisClientError(`client ${name} does not exist`); + throw new redis_provider_1.RedisClientError(`client ${name} does not exist`); } return this.redisClient.clients.get(name); } diff --git a/lib/cluster-core.module.ts b/lib/cluster-core.module.ts new file mode 100644 index 0000000..9b64a12 --- /dev/null +++ b/lib/cluster-core.module.ts @@ -0,0 +1,84 @@ +import { + DynamicModule, + Global, + Module, + Inject, + OnModuleDestroy, +} from '@nestjs/common'; +import { ModuleRef } from '@nestjs/core'; +import { Redis } from 'ioredis'; +import { + RedisClusterModuleAsyncOptions, + RedisClusterModuleOptions, +} from './cluster.interface'; +import { + createAsyncClusterOptions, + createCluster, + RedisClusterProvider, +} from './cluster.provider'; + +import { + REDIS_CLUSTER_MODULE_OPTIONS, + REDIS_CLUSTER, +} from './cluster.constants'; +import { RedisClusterService } from './cluster.service'; + +@Global() +@Module({ + providers: [RedisClusterService], + exports: [RedisClusterService], +}) +export class ClusterCoreModule implements OnModuleDestroy { + constructor( + @Inject(REDIS_CLUSTER_MODULE_OPTIONS) + private readonly options: + | RedisClusterModuleOptions + | RedisClusterModuleOptions[], + private readonly moduleRef: ModuleRef, + ) {} + + static register( + options: RedisClusterModuleOptions | RedisClusterModuleOptions[], + ): DynamicModule { + return { + module: ClusterCoreModule, + providers: [ + createCluster(), + { provide: REDIS_CLUSTER_MODULE_OPTIONS, useValue: options }, + ], + exports: [RedisClusterService], + }; + } + + static forRootAsync(options: RedisClusterModuleAsyncOptions): DynamicModule { + return { + module: ClusterCoreModule, + imports: options.imports, + providers: [createCluster(), createAsyncClusterOptions(options)], + exports: [RedisClusterService], + }; + } + + onModuleDestroy() { + const closeConnection = ({ + clusters, + defaultKey, + }: RedisClusterProvider) => options => { + const name = options.name || defaultKey; + const cluster: Redis = clusters.get(name); + + if (cluster && !options.keepAlive) { + cluster.disconnect(); + } + }; + + const provider = this.moduleRef.get(REDIS_CLUSTER); + const closeClusterConnection = closeConnection(provider); + + if (Array.isArray(this.options)) { + this.options.forEach(closeClusterConnection); + } else { + closeClusterConnection(this.options); + } + } +} diff --git a/lib/cluster.constants.ts b/lib/cluster.constants.ts new file mode 100644 index 0000000..2ef4d23 --- /dev/null +++ b/lib/cluster.constants.ts @@ -0,0 +1,4 @@ +export const REDIS_CLUSTER_MODULE_OPTIONS = Symbol( + 'REDIS_CLUSTER_MODULE_OPTIONS', +); +export const REDIS_CLUSTER = Symbol('REDIS_CLUSTER'); diff --git a/lib/cluster.interface.ts b/lib/cluster.interface.ts new file mode 100644 index 0000000..405c665 --- /dev/null +++ b/lib/cluster.interface.ts @@ -0,0 +1,20 @@ +import { ModuleMetadata } from '@nestjs/common/interfaces'; +import { Redis, ClusterOptions } from 'ioredis'; + +export interface RedisClusterModuleOptions extends ClusterOptions { + name?: string; + nodes: (string | number | object)[]; + onClusterReady?(cluster: Redis): Promise; +} + +export interface RedisClusterModuleAsyncOptions + extends Pick { + useFactory?: ( + ...args: any[] + ) => + | RedisClusterModuleOptions + | RedisClusterModuleOptions[] + | Promise + | Promise; + inject?: any[]; +} diff --git a/lib/cluster.module.ts b/lib/cluster.module.ts new file mode 100644 index 0000000..ad88054 --- /dev/null +++ b/lib/cluster.module.ts @@ -0,0 +1,26 @@ +import { DynamicModule, Module } from '@nestjs/common'; +import { + RedisClusterModuleAsyncOptions, + RedisClusterModuleOptions, +} from './cluster.interface'; + +import { ClusterCoreModule } from './cluster-core.module'; + +@Module({}) +export class RedisClusterModule { + static register( + options: RedisClusterModuleOptions | RedisClusterModuleOptions[], + ): DynamicModule { + return { + module: RedisClusterModule, + imports: [ClusterCoreModule.register(options)], + }; + } + + static forRootAsync(options: RedisClusterModuleAsyncOptions): DynamicModule { + return { + module: RedisClusterModule, + imports: [ClusterCoreModule.forRootAsync(options)], + }; + } +} diff --git a/lib/cluster.provider.ts b/lib/cluster.provider.ts new file mode 100644 index 0000000..6941db0 --- /dev/null +++ b/lib/cluster.provider.ts @@ -0,0 +1,74 @@ +import { Cluster, Redis } from 'ioredis'; +import * as uuid from 'uuid'; +import { Provider } from '@nestjs/common'; + +import { + REDIS_CLUSTER, + REDIS_CLUSTER_MODULE_OPTIONS, +} from './cluster.constants'; +import { + RedisClusterModuleAsyncOptions, + RedisClusterModuleOptions, +} from './cluster.interface'; + +export class RedisClusterError extends Error {} +export interface RedisClusterProvider { + defaultKey: string; + clusters: Map; + size: number; +} + +async function getCluster(options: RedisClusterModuleOptions): Promise { + const { onClusterReady, nodes, ...opt } = options; + const cluster: Redis = new Cluster(nodes, opt); + + if (onClusterReady) { + onClusterReady(cluster); + } + + return cluster; +} + +export const createCluster = (): Provider => ({ + provide: REDIS_CLUSTER, + useFactory: async ( + options: RedisClusterModuleOptions | RedisClusterModuleOptions[], + ): Promise => { + const clusters: Map = new Map(); + let defaultKey = uuid(); + + if (Array.isArray(options)) { + await Promise.all( + options.map(async o => { + const key: string = o.name || defaultKey; + if (clusters.has(key)) { + throw new RedisClusterError( + `${o.name || 'default'} cluster already exists`, + ); + } + clusters.set(key, await getCluster(o)); + }), + ); + } else { + if (options.name && options.name.length !== 0) { + defaultKey = options.name; + } + clusters.set(defaultKey, await getCluster(options)); + } + + return { + defaultKey, + clusters, + size: clusters.size, + }; + }, + inject: [REDIS_CLUSTER_MODULE_OPTIONS], +}); + +export const createAsyncClusterOptions = ( + options: RedisClusterModuleAsyncOptions, +) => ({ + provide: REDIS_CLUSTER_MODULE_OPTIONS, + useFactory: options.useFactory, + inject: options.inject, +}); diff --git a/lib/cluster.service.ts b/lib/cluster.service.ts new file mode 100644 index 0000000..3361455 --- /dev/null +++ b/lib/cluster.service.ts @@ -0,0 +1,26 @@ +import { Injectable, Inject } from '@nestjs/common'; +import { REDIS_CLUSTER } from './cluster.constants'; +import { Redis } from 'ioredis'; +import { RedisClusterProvider, RedisClusterError } from './cluster.provider'; + +@Injectable() +export class RedisClusterService { + constructor( + @Inject(REDIS_CLUSTER) private readonly provider: RedisClusterProvider, + ) {} + + getCluster(name?: string): Redis { + if (!name) { + name = this.provider.defaultKey; + } + + if (!this.provider.clusters.has(name)) { + throw new RedisClusterError(`cluster ${name} does not exist`); + } + return this.provider.clusters.get(name); + } + + getClusters(): Map { + return this.provider.clusters; + } +} diff --git a/lib/index.ts b/lib/index.ts index 3aae4f1..8c026e3 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -1,3 +1,6 @@ export * from './redis.service'; export * from './redis.module'; export * from './redis.interface'; +export * from './cluster.service'; +export * from './cluster.module'; +export * from './cluster.interface'; diff --git a/lib/redis-core.module.ts b/lib/redis-core.module.ts index baabc58..fd8ce1c 100644 --- a/lib/redis-core.module.ts +++ b/lib/redis-core.module.ts @@ -11,7 +11,7 @@ import { createAsyncClientOptions, createClient, RedisClient, -} from './redis-client.provider'; +} from './redis.provider'; import { REDIS_MODULE_OPTIONS, REDIS_CLIENT } from './redis.constants'; import { RedisService } from './redis.service'; diff --git a/lib/redis.interface.ts b/lib/redis.interface.ts index a6d4db0..e9f6e43 100644 --- a/lib/redis.interface.ts +++ b/lib/redis.interface.ts @@ -7,7 +7,8 @@ export interface RedisModuleOptions extends RedisOptions { onClientReady?(client: Redis): Promise; } -export interface RedisModuleAsyncOptions extends Pick { +export interface RedisModuleAsyncOptions + extends Pick { useFactory?: ( ...args: any[] ) => diff --git a/lib/redis-client.provider.ts b/lib/redis.provider.ts similarity index 100% rename from lib/redis-client.provider.ts rename to lib/redis.provider.ts diff --git a/lib/redis.service.ts b/lib/redis.service.ts index 4da6548..ade4b82 100644 --- a/lib/redis.service.ts +++ b/lib/redis.service.ts @@ -1,7 +1,7 @@ import { Injectable, Inject } from '@nestjs/common'; import { REDIS_CLIENT } from './redis.constants'; import * as Redis from 'ioredis'; -import { RedisClient, RedisClientError } from './redis-client.provider'; +import { RedisClient, RedisClientError } from './redis.provider'; @Injectable() export class RedisService { diff --git a/package.json b/package.json index 7171481..189d7cf 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { - "name": "nestjs-redis", - "version": "1.2.7", - "description": "a nestjs redis module", - "author": "zzy", + "name": "nestjs-redis-cluster", + "version": "1.2.10", + "description": "A nestjs redis module w/ cluster support", + "author": "Ishmael Samuel (useparagon.com)", "license": "MIT", "repository": { "type": "git", - "url": "https://github.com/kyknow/nestjs-redis" + "url": "https://github.com/useparagon.com/nestjs-redis-cluster" }, "scripts": { "build": "rimraf dist && tsc -p tsconfig.json", @@ -40,7 +40,7 @@ "tslint": "^5.20.1", "tslint-config-prettier": "^1.18.0", "tslint-plugin-prettier": "^2.0.1", - "typescript": "^2.4.2" + "typescript": "^3.6.4" }, "keywords": [ "nestjs", diff --git a/yarn.lock b/yarn.lock index a994f54..7a99950 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3405,9 +3405,10 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" -typescript@^2.4.2: - version "2.9.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.2.tgz#1cbf61d05d6b96269244eb6a3bce4bd914e0f00c" +typescript@^3.6.4: + version "3.9.6" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.6.tgz#8f3e0198a34c3ae17091b35571d3afd31999365a" + integrity sha512-Pspx3oKAPJtjNwE92YS05HQoY7z2SFyOpHo9MqJor3BXAGNaPUs83CuVp9VISFkSjyRfiTpmKuAYGJB7S7hOxw== uglify-js@^3.1.4: version "3.7.1"