From d75a624ae51b5a4a501199ad433a1e18676e8012 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Tue, 16 Feb 2021 12:42:03 +0530 Subject: [PATCH] style: reformat source files --- .circleci/config.yml | 41 +- .editorconfig | 4 +- .eslintrc.json | 5 +- .prettierignore | 1 + .prettierrc | 16 +- adonis-typings/container.ts | 8 +- adonis-typings/events.ts | 28 +- adonis-typings/redis.ts | 434 ++++++------- config.json | 3 +- japaFile.js | 2 +- package.json | 4 +- providers/RedisProvider.ts | 126 ++-- src/AbstractConnection/index.ts | 744 +++++++++++----------- src/Bindings/Repl.ts | 28 +- src/RedisClusterConnection/index.ts | 56 +- src/RedisConnection/index.ts | 60 +- src/RedisManager/index.ts | 396 ++++++------ src/ioMethods.ts | 320 +++++----- test/contracts.ts | 8 +- test/redis-cluster-factory.spec.ts | 396 ++++++------ test/redis-connection.spec.ts | 930 ++++++++++++++-------------- test/redis-provider.spec.ts | 242 ++++---- test/redis.spec.ts | 450 +++++++------- tsconfig.json | 12 +- 24 files changed, 2171 insertions(+), 2143 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 9c92c7c..185e8a9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,17 +1,42 @@ -version: 2.1 -orbs: +version: 2 +orbs: null aws-ecr: circleci/aws-ecr@4.0.1 jobs: - tests: + build_14.15.4: docker: - - image: 'bayesimpact/circleci' + - image: circleci/node:14.15.4 working_directory: ~/app steps: - checkout - - setup_remote_docker - - run: docker-compose run --rm tests + - restore_cache: + keys: + - v1-dependencies-{{ checksum "package.json" }} + - v1-dependencies- + - run: npm install + - save_cache: + paths: + - node_modules + key: v1-dependencies-{{ checksum "package.json" }} + - run: npm test + build_latest: + docker: + - image: circleci/node:latest + working_directory: ~/app + steps: + - checkout + - restore_cache: + keys: + - v1-dependencies-{{ checksum "package.json" }} + - v1-dependencies- + - run: npm install + - save_cache: + paths: + - node_modules + key: v1-dependencies-{{ checksum "package.json" }} + - run: npm test workflows: - version: 2.1 + version: 2 workflow: jobs: - - tests + - build_14.15.4 + - build_latest diff --git a/.editorconfig b/.editorconfig index bf657a0..c96a5c2 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,7 +3,7 @@ root = true [*] indent_size = 2 -indent_style = tab +indent_style = space end_of_line = lf charset = utf-8 trim_trailing_whitespace = true @@ -20,4 +20,4 @@ indent_style = ignore insert_final_newline = ignore [MakeFile] -indent_style = tab +indent_style = space diff --git a/.eslintrc.json b/.eslintrc.json index b619952..5c2b0f2 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -9,7 +9,10 @@ ], "rules": { "prettier/prettier": [ - "error" + "error", + { + "endOfLine": "auto" + } ] } } diff --git a/.prettierignore b/.prettierignore index 3d3092d..e843a17 100644 --- a/.prettierignore +++ b/.prettierignore @@ -5,3 +5,4 @@ config.json .eslintrc.json package.json *.html +*.txt diff --git a/.prettierrc b/.prettierrc index f08a117..07634f7 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,10 +1,10 @@ { - "trailingComma": "es5", - "semi": false, - "singleQuote": true, - "useTabs": true, - "quoteProps": "consistent", - "bracketSpacing": true, - "arrowParens": "always", - "printWidth": 100 + "trailingComma": "es5", + "semi": false, + "singleQuote": true, + "useTabs": false, + "quoteProps": "consistent", + "bracketSpacing": true, + "arrowParens": "always", + "printWidth": 100 } diff --git a/adonis-typings/container.ts b/adonis-typings/container.ts index d616e9e..87323c1 100644 --- a/adonis-typings/container.ts +++ b/adonis-typings/container.ts @@ -8,9 +8,9 @@ */ declare module '@ioc:Adonis/Core/Application' { - import { RedisManagerContract } from '@ioc:Adonis/Addons/Redis' + import { RedisManagerContract } from '@ioc:Adonis/Addons/Redis' - export interface ContainerBindings { - 'Adonis/Addons/Redis': RedisManagerContract - } + export interface ContainerBindings { + 'Adonis/Addons/Redis': RedisManagerContract + } } diff --git a/adonis-typings/events.ts b/adonis-typings/events.ts index 9fca117..9c7ee3d 100644 --- a/adonis-typings/events.ts +++ b/adonis-typings/events.ts @@ -8,20 +8,20 @@ */ declare module '@ioc:Adonis/Core/Event' { - import { Redis } from 'ioredis' - import { RedisClusterConnectionContract, RedisConnectionContract } from '@ioc:Adonis/Addons/Redis' + import { Redis } from 'ioredis' + import { RedisClusterConnectionContract, RedisConnectionContract } from '@ioc:Adonis/Addons/Redis' - interface EventsList { - 'redis:ready': { connection: RedisClusterConnectionContract | RedisConnectionContract } - 'redis:connect': { connection: RedisClusterConnectionContract | RedisConnectionContract } - 'redis:error': { - error: any - connection: RedisClusterConnectionContract | RedisConnectionContract - } - 'redis:end': { connection: RedisClusterConnectionContract | RedisConnectionContract } + interface EventsList { + 'redis:ready': { connection: RedisClusterConnectionContract | RedisConnectionContract } + 'redis:connect': { connection: RedisClusterConnectionContract | RedisConnectionContract } + 'redis:error': { + error: any + connection: RedisClusterConnectionContract | RedisConnectionContract + } + 'redis:end': { connection: RedisClusterConnectionContract | RedisConnectionContract } - 'node:added': { connection: RedisClusterConnectionContract; node: Redis } - 'node:removed': { connection: RedisClusterConnectionContract; node: Redis } - 'node:error': { error: any; connection: RedisClusterConnectionContract; address: string } - } + 'node:added': { connection: RedisClusterConnectionContract; node: Redis } + 'node:removed': { connection: RedisClusterConnectionContract; node: Redis } + 'node:error': { error: any; connection: RedisClusterConnectionContract; address: string } + } } diff --git a/adonis-typings/redis.ts b/adonis-typings/redis.ts index a1ce724..e9e2d02 100644 --- a/adonis-typings/redis.ts +++ b/adonis-typings/redis.ts @@ -8,238 +8,238 @@ */ declare module '@ioc:Adonis/Addons/Redis' { - import { EventEmitter } from 'events' - import { HealthReportEntry } from '@ioc:Adonis/Core/HealthCheck' - import { Redis, RedisOptions, ClusterOptions, Cluster, NodeRole } from 'ioredis' + import { EventEmitter } from 'events' + import { HealthReportEntry } from '@ioc:Adonis/Core/HealthCheck' + import { Redis, RedisOptions, ClusterOptions, Cluster, NodeRole } from 'ioredis' - /* + /* |-------------------------------------------------------------------------- | Helpers |-------------------------------------------------------------------------- */ - /** - * Returns factory for a given connection by inspecting it's config. - */ - type GetConnectionFactoryType< - T extends keyof RedisConnectionsList - > = RedisConnectionsList[T] extends RedisClusterConfig - ? RedisClusterConnectionContract - : RedisConnectionContract - - /* + /** + * Returns factory for a given connection by inspecting it's config. + */ + type GetConnectionFactoryType< + T extends keyof RedisConnectionsList + > = RedisConnectionsList[T] extends RedisClusterConfig + ? RedisClusterConnectionContract + : RedisConnectionContract + + /* |-------------------------------------------------------------------------- | PubSub |-------------------------------------------------------------------------- */ - /** - * Pubsub subscriber - */ - export type PubSubChannelHandler = (data: T) => Promise | void - export type PubSubPatternHandler = ( - channel: string, - data: T - ) => Promise | void - - /** - * Redis pub/sub methods - */ - export interface RedisPubSubContract { - subscribe(channel: string, handler: PubSubChannelHandler | string): void - psubscribe(pattern: string, handler: PubSubPatternHandler | string): void - unsubscribe(channel: string): void - punsubscribe(pattern: string): void - } - - /** - * Shape of the report node for the redis connection report - */ - export type HealthReportNode = { - connection: string - status: string - used_memory: string - error: any - } - - /** - * List of commands on the IORedis. We omit their internal events and pub/sub - * handlers, since we our own. - */ - export type IORedisCommands = Omit< - Redis, - | 'Promise' - | 'status' - | 'connect' - | 'disconnect' - | 'duplicate' - | 'subscribe' - | 'unsubscribe' - | 'psubscribe' - | 'punsubscribe' - | 'quit' - | keyof EventEmitter - > - - /* + /** + * Pubsub subscriber + */ + export type PubSubChannelHandler = (data: T) => Promise | void + export type PubSubPatternHandler = ( + channel: string, + data: T + ) => Promise | void + + /** + * Redis pub/sub methods + */ + export interface RedisPubSubContract { + subscribe(channel: string, handler: PubSubChannelHandler | string): void + psubscribe(pattern: string, handler: PubSubPatternHandler | string): void + unsubscribe(channel: string): void + punsubscribe(pattern: string): void + } + + /** + * Shape of the report node for the redis connection report + */ + export type HealthReportNode = { + connection: string + status: string + used_memory: string + error: any + } + + /** + * List of commands on the IORedis. We omit their internal events and pub/sub + * handlers, since we our own. + */ + export type IORedisCommands = Omit< + Redis, + | 'Promise' + | 'status' + | 'connect' + | 'disconnect' + | 'duplicate' + | 'subscribe' + | 'unsubscribe' + | 'psubscribe' + | 'punsubscribe' + | 'quit' + | keyof EventEmitter + > + + /* |-------------------------------------------------------------------------- | Redis Connections |-------------------------------------------------------------------------- */ - /** - * Standard Redis Connection - */ - export interface RedisConnectionContract - extends IORedisCommands, - RedisPubSubContract, - EventEmitter { - status: string - connectionName: string - subscriberStatus?: string - ioConnection: Redis - ioSubscriberConnection?: Redis - - connect(callback?: () => void): Promise - disconnect(): Promise - getReport(checkForMemory?: boolean): Promise - quit(): Promise - } - - /** - * Redis cluster factory interface - */ - export interface RedisClusterConnectionContract - extends IORedisCommands, - RedisPubSubContract, - EventEmitter { - status: string - connectionName: string - subscriberStatus?: string - ioConnection: Cluster - ioSubscriberConnection?: Cluster - - getReport(checkForMemory?: boolean): Promise - connect(callback?: () => void): Promise - nodes(role?: NodeRole): Redis[] - disconnect(): Promise - quit(): Promise - } - - type Connection = RedisClusterConnectionContract | RedisConnectionContract - - /** - * Redis manager exposes the API to intertact with a redis server. One can make - * use of multiple redis connections by defining them inside `config/redis` - * file. - * - * ```ts - * Redis.connection() // default connection - * Redis.connection('primary') // named connection - * ``` - */ - export interface RedisBaseManagerContract { - /** - * A boolean to know whether health checks have been enabled on one - * or more redis connections or not. - */ - healthChecksEnabled: boolean - - /** - * Number of active redis connection. - */ - activeConnectionsCount: number - activeConnections: { [key: string]: Connection } - - /** - * Fetch a named connection from the defined config inside config/redis file - */ - connection( - name: ConnectionName - ): GetConnectionFactoryType - - /** - * Untyped connection - */ - connection(name: string): RedisConnectionContract | RedisClusterConnectionContract - - /** - * Returns the default connection client - */ - connection(): RedisConnectionContract | RedisClusterConnectionContract - - /** - * Returns the healthcheck report - */ - report(): Promise - - /** - * Quit a named connection. - */ - quit(name?: ConnectionName): Promise - quit(name?: string): Promise - - /** - * Forcefully disconnect a named connection. - */ - disconnect( - name?: ConnectionName - ): Promise - disconnect(name?: string): Promise - - /** - * Quit all redis connections - */ - quitAll(): Promise - - /** - * Disconnect all redis connections - */ - disconnectAll(): Promise - } - - /* + /** + * Standard Redis Connection + */ + export interface RedisConnectionContract + extends IORedisCommands, + RedisPubSubContract, + EventEmitter { + status: string + connectionName: string + subscriberStatus?: string + ioConnection: Redis + ioSubscriberConnection?: Redis + + connect(callback?: () => void): Promise + disconnect(): Promise + getReport(checkForMemory?: boolean): Promise + quit(): Promise + } + + /** + * Redis cluster factory interface + */ + export interface RedisClusterConnectionContract + extends IORedisCommands, + RedisPubSubContract, + EventEmitter { + status: string + connectionName: string + subscriberStatus?: string + ioConnection: Cluster + ioSubscriberConnection?: Cluster + + getReport(checkForMemory?: boolean): Promise + connect(callback?: () => void): Promise + nodes(role?: NodeRole): Redis[] + disconnect(): Promise + quit(): Promise + } + + type Connection = RedisClusterConnectionContract | RedisConnectionContract + + /** + * Redis manager exposes the API to intertact with a redis server. One can make + * use of multiple redis connections by defining them inside `config/redis` + * file. + * + * ```ts + * Redis.connection() // default connection + * Redis.connection('primary') // named connection + * ``` + */ + export interface RedisBaseManagerContract { + /** + * A boolean to know whether health checks have been enabled on one + * or more redis connections or not. + */ + healthChecksEnabled: boolean + + /** + * Number of active redis connection. + */ + activeConnectionsCount: number + activeConnections: { [key: string]: Connection } + + /** + * Fetch a named connection from the defined config inside config/redis file + */ + connection( + name: ConnectionName + ): GetConnectionFactoryType + + /** + * Untyped connection + */ + connection(name: string): RedisConnectionContract | RedisClusterConnectionContract + + /** + * Returns the default connection client + */ + connection(): RedisConnectionContract | RedisClusterConnectionContract + + /** + * Returns the healthcheck report + */ + report(): Promise + + /** + * Quit a named connection. + */ + quit(name?: ConnectionName): Promise + quit(name?: string): Promise + + /** + * Forcefully disconnect a named connection. + */ + disconnect( + name?: ConnectionName + ): Promise + disconnect(name?: string): Promise + + /** + * Quit all redis connections + */ + quitAll(): Promise + + /** + * Disconnect all redis connections + */ + disconnectAll(): Promise + } + + /* |-------------------------------------------------------------------------- | Config |-------------------------------------------------------------------------- */ - /** - * Shape of standard Redis connection config - */ - export type RedisConnectionConfig = Omit & { - healthCheck?: boolean - port?: string | number - } - - /** - * Shape of cluster config - */ - export type RedisClusterConfig = { - clusters: { host: string; port: number | string }[] - clusterOptions?: ClusterOptions - healthCheck?: boolean - } - - /** - * A list of typed connections defined in the user land using - * the contracts file - */ - export interface RedisConnectionsList {} - - /** - * Define the config properties on this interface and they will appear - * everywhere. - */ - export interface RedisConfig { - connection: keyof RedisConnectionsList - connections: { [P in keyof RedisConnectionsList]: RedisConnectionsList[P] } - } - - /** - * Redis manager proxies all IO methods on the connection - */ - export interface RedisManagerContract - extends RedisBaseManagerContract, - IORedisCommands, - RedisPubSubContract {} - - const Redis: RedisManagerContract - export default Redis + /** + * Shape of standard Redis connection config + */ + export type RedisConnectionConfig = Omit & { + healthCheck?: boolean + port?: string | number + } + + /** + * Shape of cluster config + */ + export type RedisClusterConfig = { + clusters: { host: string; port: number | string }[] + clusterOptions?: ClusterOptions + healthCheck?: boolean + } + + /** + * A list of typed connections defined in the user land using + * the contracts file + */ + export interface RedisConnectionsList {} + + /** + * Define the config properties on this interface and they will appear + * everywhere. + */ + export interface RedisConfig { + connection: keyof RedisConnectionsList + connections: { [P in keyof RedisConnectionsList]: RedisConnectionsList[P] } + } + + /** + * Redis manager proxies all IO methods on the connection + */ + export interface RedisManagerContract + extends RedisBaseManagerContract, + IORedisCommands, + RedisPubSubContract {} + + const Redis: RedisManagerContract + export default Redis } diff --git a/config.json b/config.json index 67f17b4..2b61fa9 100644 --- a/config.json +++ b/config.json @@ -1,11 +1,10 @@ { "core": true, - "ts": true, "license": "MIT", "services": [ "circleci" ], - "minNodeVersion": "12.18.2", + "minNodeVersion": "14.15.4", "probotApps": [ "stale", "lock" diff --git a/japaFile.js b/japaFile.js index 0e49f1c..e87aae4 100644 --- a/japaFile.js +++ b/japaFile.js @@ -2,5 +2,5 @@ require('@adonisjs/require-ts/build/register') const { configure } = require('japa') configure({ - files: ['test/**/*.spec.ts'], + files: ['test/**/*.spec.ts'], }) diff --git a/package.json b/package.json index 0b4d23e..6bb0c2d 100644 --- a/package.json +++ b/package.json @@ -14,11 +14,11 @@ "scripts": { "mrm": "mrm --preset=@adonisjs/mrm-preset", "pretest": "npm run lint", - "test": "docker-compose build && docker-compose run --rm tests", + "test": "node japaFile.js", "clean": "del build", "copyfiles": "copyfiles \"templates/**/*.txt\" \"instructions.md\" build", "compile": "npm run lint && npm run clean && tsc", - "build": "npm run compile && npm run copyfiles", + "build": "npm run compile", "commit": "git-cz", "release": "np", "version": "npm run build", diff --git a/providers/RedisProvider.ts b/providers/RedisProvider.ts index 41bddc8..7d653c4 100644 --- a/providers/RedisProvider.ts +++ b/providers/RedisProvider.ts @@ -13,75 +13,75 @@ import { ApplicationContract } from '@ioc:Adonis/Core/Application' * Provider to bind redis to the container */ export default class RedisProvider { - constructor(protected app: ApplicationContract) {} - public static needsApplication = true + constructor(protected app: ApplicationContract) {} + public static needsApplication = true - /** - * Register redis health check - */ - protected registerHealthCheck() { - /** - * Do not register healthcheck when not running in web - * or test mode - */ - if (!['web', 'test'].includes(this.app.environment)) { - return - } + /** + * Register redis health check + */ + protected registerHealthCheck() { + /** + * Do not register healthcheck when not running in web + * or test mode + */ + if (!['web', 'test'].includes(this.app.environment)) { + return + } - this.app.container.withBindings( - ['Adonis/Core/HealthCheck', 'Adonis/Addons/Redis'], - (HealthCheck, Redis) => { - if (Redis.healthChecksEnabled) { - HealthCheck.addChecker('redis', 'Adonis/Addons/Redis') - } - } - ) - } + this.app.container.withBindings( + ['Adonis/Core/HealthCheck', 'Adonis/Addons/Redis'], + (HealthCheck, Redis) => { + if (Redis.healthChecksEnabled) { + HealthCheck.addChecker('redis', 'Adonis/Addons/Redis') + } + } + ) + } - /** - * Define repl bindings - */ - protected defineReplBindings() { - /** - * Do not register repl bindings when not running in "repl" - * environment - */ - if (this.app.environment !== 'repl') { - return - } + /** + * Define repl bindings + */ + protected defineReplBindings() { + /** + * Do not register repl bindings when not running in "repl" + * environment + */ + if (this.app.environment !== 'repl') { + return + } - this.app.container.withBindings(['Adonis/Addons/Repl'], (Repl) => { - const { defineReplBindings } = require('../src/Bindings/Repl') - defineReplBindings(this.app, Repl) - }) - } + this.app.container.withBindings(['Adonis/Addons/Repl'], (Repl) => { + const { defineReplBindings } = require('../src/Bindings/Repl') + defineReplBindings(this.app, Repl) + }) + } - /** - * Register the redis binding - */ - public register() { - this.app.container.singleton('Adonis/Addons/Redis', () => { - const config = this.app.container.resolveBinding('Adonis/Core/Config').get('redis', {}) - const emitter = this.app.container.resolveBinding('Adonis/Core/Event') - const { RedisManager } = require('../src/RedisManager') + /** + * Register the redis binding + */ + public register() { + this.app.container.singleton('Adonis/Addons/Redis', () => { + const config = this.app.container.resolveBinding('Adonis/Core/Config').get('redis', {}) + const emitter = this.app.container.resolveBinding('Adonis/Core/Event') + const { RedisManager } = require('../src/RedisManager') - return new RedisManager(this.app, config, emitter) - }) - } + return new RedisManager(this.app, config, emitter) + }) + } - /** - * Registering the health check checker with HealthCheck service - */ - public boot() { - this.registerHealthCheck() - this.defineReplBindings() - } + /** + * Registering the health check checker with HealthCheck service + */ + public boot() { + this.registerHealthCheck() + this.defineReplBindings() + } - /** - * Gracefully shutdown connections when app goes down - */ - public async shutdown() { - const Redis = this.app.container.resolveBinding('Adonis/Addons/Redis') - await Redis.quitAll() - } + /** + * Gracefully shutdown connections when app goes down + */ + public async shutdown() { + const Redis = this.app.container.resolveBinding('Adonis/Addons/Redis') + await Redis.quitAll() + } } diff --git a/src/AbstractConnection/index.ts b/src/AbstractConnection/index.ts index b690188..8c61499 100644 --- a/src/AbstractConnection/index.ts +++ b/src/AbstractConnection/index.ts @@ -16,9 +16,9 @@ import { ApplicationContract } from '@ioc:Adonis/Core/Application' import { ContainerBindings, IocResolverContract } from '@ioc:Adonis/Core/Application' import { - HealthReportNode, - PubSubChannelHandler, - PubSubPatternHandler, + HealthReportNode, + PubSubChannelHandler, + PubSubPatternHandler, } from '@ioc:Adonis/Addons/Redis' /** @@ -31,373 +31,373 @@ const sleep = () => new Promise((resolve) => setTimeout(resolve, 1000)) * and normal Redis connections. */ export abstract class AbstractConnection extends EventEmitter { - /** - * Reference to the main ioRedis connection - */ - public ioConnection: T - - /** - * Reference to the main ioRedis subscriber connection - */ - public ioSubscriberConnection?: T - - /** - * Number of times `getReport` was deferred, at max we defer it for 3 times - */ - private deferredReportAttempts = 0 - - /** - * The last error emitted by the `error` event. We set it to `null` after - * the `ready` event - */ - private lastError?: any - - /** - * IoCResolver to resolve bindings - */ - private resolver: IocResolverContract - - /** - * A list of active subscription and pattern subscription - */ - protected subscriptions: Map = new Map() - protected psubscriptions: Map = new Map() - - /** - * Returns an anonymous function by parsing the IoC container - * binding. - */ - private resolveIoCBinding(handler: string): PubSubChannelHandler | PubSubPatternHandler { - return (...args: any[]) => { - return this.resolver.call(handler, undefined, args) - } - } - - /** - * Returns the memory usage for a given connection - */ - private async getUsedMemory() { - const memory = await (this.ioConnection as Redis).info('memory') - const memorySegment = memory - .split(/\r|\r\n/) - .find((line) => line.trim().startsWith('used_memory_human')) - return memorySegment ? memorySegment.split(':')[1] : 'unknown' - } - - /** - * Returns status of the main connection - */ - public get status(): string { - return (this.ioConnection as Redis).status - } - - /** - * Returns status of the subscriber connection or - * undefined when there is no subscriber - * connection - */ - public get subscriberStatus(): string | undefined { - if (!this.ioSubscriberConnection) { - return - } - - return (this.ioSubscriberConnection as Redis).status - } - - /** - * Parent class must implement makeSubscriberConnection - */ - protected abstract makeSubscriberConnection(): void - - constructor(public connectionName: string, application: ApplicationContract) { - super() - this.resolver = application.container.getResolver(undefined, 'redisListeners', 'App/Listeners') - } - - /** - * The events proxying is required, since ioredis itself doesn't cleanup - * listeners after closing the redis connection and since closing a - * connection is an async operation, we have to wait for the `end` - * event on the actual connection and then remove listeners. - */ - protected proxyConnectionEvents() { - this.ioConnection.on('connect', () => this.emit('connect', this)) - this.ioConnection.on('ready', () => { - /** - * We must set the error to null when server is ready for accept - * command - */ - this.lastError = null - this.emit('ready', this) - }) - - this.ioConnection.on('error', (error: any) => { - this.lastError = error - this.emit('error', error, this) - }) - - this.ioConnection.on('close', () => this.emit('close', this)) - this.ioConnection.on('reconnecting', () => this.emit('reconnecting', this)) - - /** - * Cluster only events - */ - this.ioConnection.on('+node', (node: Redis) => this.emit('node:added', this, node)) - this.ioConnection.on('-node', (node: Redis) => this.emit('node:removed', this, node)) - this.ioConnection.on('node error', (error: any, address: string) => { - this.emit('node:error', error, address, this) - }) - - /** - * On end, we must cleanup client and self listeners - */ - this.ioConnection.on('end', async () => { - this.ioConnection.removeAllListeners() - this.emit('end', this) - this.removeAllListeners() - }) - } - - /** - * Making the subscriber connection and proxying it's events. The method - * results in a noop, in case of an existing subscriber connection. - */ - protected setupSubscriberConnection() { - if (this.ioSubscriberConnection) { - return - } - - /** - * Ask parent class to setup the subscriber connection - */ - this.makeSubscriberConnection() - - /** - * Listen for messages - */ - this.ioSubscriberConnection!.on('message', (channel, message) => { - const handler = this.subscriptions.get(channel) - if (handler) { - handler(message) - } - }) - - /** - * Listen for pattern messages - */ - this.ioSubscriberConnection!.on('pmessage', (pattern, channel, message) => { - const handler = this.psubscriptions.get(pattern) - if (handler) { - handler(channel, message) - } - }) - - /** - * Proxying subscriber events, so that we can prefix them with `subscriber:`. - * Also make sure not to clear the events of this class on subscriber - * disconnect - */ - this.ioSubscriberConnection!.on('connect', () => this.emit('subscriber:connect', this)) - this.ioSubscriberConnection!.on('ready', () => this.emit('subscriber:ready', this)) - this.ioSubscriberConnection!.on('error', (error: any) => - this.emit('subscriber:error', error, this) - ) - this.ioSubscriberConnection!.on('close', () => this.emit('subscriber:close', this)) - this.ioSubscriberConnection!.on('reconnecting', () => - this.emit('subscriber:reconnecting', this) - ) - - /** - * On subscriber connection end, we must clear registered - * subscriptions and client event listeners. - */ - this.ioSubscriberConnection!.on('end', async () => { - this.ioConnection.removeAllListeners() - this.emit('subscriber:end', this) - - /** - * Cleanup subscriptions map - */ - this.subscriptions.clear() - this.psubscriptions.clear() - }) - } - - /** - * Gracefully end the redis connection - */ - public async quit() { - await this.ioConnection.quit() - if (this.ioSubscriberConnection) { - await this.ioSubscriberConnection.quit() - } - } - - /** - * Forcefully end the redis connection - */ - public async disconnect() { - await this.ioConnection.disconnect() - if (this.ioSubscriberConnection) { - await this.ioSubscriberConnection.disconnect() - } - } - - /** - * Subscribe to a given channel to receive Redis pub/sub events. A - * new subscriber connection will be created/managed automatically. - */ - public subscribe(channel: string, handler: PubSubChannelHandler | string): void { - /** - * Make the subscriber connection. The method results in a noop when - * subscriber connection already exists. - */ - this.setupSubscriberConnection() - - /** - * Disallow multiple subscriptions to a single channel - */ - if (this.subscriptions.has(channel)) { - throw new Exception( - `"${channel}" channel already has an active subscription`, - 500, - 'E_MULTIPLE_REDIS_SUBSCRIPTIONS' - ) - } - - /** - * If the subscriptions map is empty, it means we have no active subscriptions - * on the given channel, hence we should make one subscription and also set - * the subscription handler. - */ - const connection = this.ioSubscriberConnection as Redis - connection - .subscribe(channel) - .then((count) => { - if (typeof handler === 'string') { - handler = this.resolveIoCBinding(handler) as PubSubChannelHandler - } - this.emit('subscription:ready', count, this) - this.subscriptions.set(channel, handler) - }) - .catch((error) => { - this.emit('subscription:error', error, this) - }) - } - - /** - * Unsubscribe from a channel - */ - public unsubscribe(channel: string) { - this.subscriptions.delete(channel) - return (this.ioSubscriberConnection as Redis).unsubscribe(channel) - } - - /** - * Make redis subscription for a pattern - */ - public psubscribe(pattern: string, handler: PubSubPatternHandler | string): void { - /** - * Make the subscriber connection. The method results in a noop when - * subscriber connection already exists. - */ - this.setupSubscriberConnection() - - /** - * Disallow multiple subscriptions to a single channel - */ - if (this.psubscriptions.has(pattern)) { - throw new Exception( - `${pattern} pattern already has an active subscription`, - 500, - 'E_MULTIPLE_REDIS_PSUBSCRIPTIONS' - ) - } - - /** - * If the subscriptions map is empty, it means we have no active subscriptions - * on the given channel, hence we should make one subscription and also set - * the subscription handler. - */ - const connection = this.ioSubscriberConnection as Redis - - connection - .psubscribe(pattern) - .then((count) => { - if (typeof handler === 'string') { - handler = this.resolveIoCBinding(handler) as PubSubPatternHandler - } - - this.emit('psubscription:ready', count, this) - this.psubscriptions.set(pattern, handler) - }) - .catch((error) => { - this.emit('psubscription:error', error, this) - }) - } - - /** - * Unsubscribe from a given pattern - */ - public punsubscribe(pattern: string) { - this.psubscriptions.delete(pattern) - return (this.ioSubscriberConnection as any).punsubscribe(pattern) - } - - /** - * Returns report for the connection - */ - public async getReport(checkForMemory?: boolean): Promise { - const connection = this.ioConnection as Redis - - /** - * When status === 'connecting' we maximum wait for 3 times and then send - * the report. Which means, if we are unable to connect to redis within - * 3 seconds, we consider the connection unstable. - */ - if (connection.status === 'connecting' && this.deferredReportAttempts < 3 && !this.lastError) { - await sleep() - this.deferredReportAttempts++ - return this.getReport(checkForMemory) - } - - /** - * Returns the status with the last error when connection status - * is not in `connect` state. - */ - if (!['ready', 'connect'].includes(connection.status)) { - return { - connection: this.connectionName, - status: connection.status, - used_memory: 'unknown', - error: this.lastError, - } - } - - try { - /** - * Ping the server for response - */ - await connection.ping() - - /** - * Collect memory when checkForMemory = true - */ - const memory = checkForMemory ? await this.getUsedMemory() : 'unknown' - - return { - connection: this.connectionName, - status: connection.status, - used_memory: memory, - error: null, - } - } catch (error) { - return { - connection: this.connectionName, - status: connection.status, - used_memory: 'unknown', - error, - } - } - } + /** + * Reference to the main ioRedis connection + */ + public ioConnection: T + + /** + * Reference to the main ioRedis subscriber connection + */ + public ioSubscriberConnection?: T + + /** + * Number of times `getReport` was deferred, at max we defer it for 3 times + */ + private deferredReportAttempts = 0 + + /** + * The last error emitted by the `error` event. We set it to `null` after + * the `ready` event + */ + private lastError?: any + + /** + * IoCResolver to resolve bindings + */ + private resolver: IocResolverContract + + /** + * A list of active subscription and pattern subscription + */ + protected subscriptions: Map = new Map() + protected psubscriptions: Map = new Map() + + /** + * Returns an anonymous function by parsing the IoC container + * binding. + */ + private resolveIoCBinding(handler: string): PubSubChannelHandler | PubSubPatternHandler { + return (...args: any[]) => { + return this.resolver.call(handler, undefined, args) + } + } + + /** + * Returns the memory usage for a given connection + */ + private async getUsedMemory() { + const memory = await (this.ioConnection as Redis).info('memory') + const memorySegment = memory + .split(/\r|\r\n/) + .find((line) => line.trim().startsWith('used_memory_human')) + return memorySegment ? memorySegment.split(':')[1] : 'unknown' + } + + /** + * Returns status of the main connection + */ + public get status(): string { + return (this.ioConnection as Redis).status + } + + /** + * Returns status of the subscriber connection or + * undefined when there is no subscriber + * connection + */ + public get subscriberStatus(): string | undefined { + if (!this.ioSubscriberConnection) { + return + } + + return (this.ioSubscriberConnection as Redis).status + } + + /** + * Parent class must implement makeSubscriberConnection + */ + protected abstract makeSubscriberConnection(): void + + constructor(public connectionName: string, application: ApplicationContract) { + super() + this.resolver = application.container.getResolver(undefined, 'redisListeners', 'App/Listeners') + } + + /** + * The events proxying is required, since ioredis itself doesn't cleanup + * listeners after closing the redis connection and since closing a + * connection is an async operation, we have to wait for the `end` + * event on the actual connection and then remove listeners. + */ + protected proxyConnectionEvents() { + this.ioConnection.on('connect', () => this.emit('connect', this)) + this.ioConnection.on('ready', () => { + /** + * We must set the error to null when server is ready for accept + * command + */ + this.lastError = null + this.emit('ready', this) + }) + + this.ioConnection.on('error', (error: any) => { + this.lastError = error + this.emit('error', error, this) + }) + + this.ioConnection.on('close', () => this.emit('close', this)) + this.ioConnection.on('reconnecting', () => this.emit('reconnecting', this)) + + /** + * Cluster only events + */ + this.ioConnection.on('+node', (node: Redis) => this.emit('node:added', this, node)) + this.ioConnection.on('-node', (node: Redis) => this.emit('node:removed', this, node)) + this.ioConnection.on('node error', (error: any, address: string) => { + this.emit('node:error', error, address, this) + }) + + /** + * On end, we must cleanup client and self listeners + */ + this.ioConnection.on('end', async () => { + this.ioConnection.removeAllListeners() + this.emit('end', this) + this.removeAllListeners() + }) + } + + /** + * Making the subscriber connection and proxying it's events. The method + * results in a noop, in case of an existing subscriber connection. + */ + protected setupSubscriberConnection() { + if (this.ioSubscriberConnection) { + return + } + + /** + * Ask parent class to setup the subscriber connection + */ + this.makeSubscriberConnection() + + /** + * Listen for messages + */ + this.ioSubscriberConnection!.on('message', (channel, message) => { + const handler = this.subscriptions.get(channel) + if (handler) { + handler(message) + } + }) + + /** + * Listen for pattern messages + */ + this.ioSubscriberConnection!.on('pmessage', (pattern, channel, message) => { + const handler = this.psubscriptions.get(pattern) + if (handler) { + handler(channel, message) + } + }) + + /** + * Proxying subscriber events, so that we can prefix them with `subscriber:`. + * Also make sure not to clear the events of this class on subscriber + * disconnect + */ + this.ioSubscriberConnection!.on('connect', () => this.emit('subscriber:connect', this)) + this.ioSubscriberConnection!.on('ready', () => this.emit('subscriber:ready', this)) + this.ioSubscriberConnection!.on('error', (error: any) => + this.emit('subscriber:error', error, this) + ) + this.ioSubscriberConnection!.on('close', () => this.emit('subscriber:close', this)) + this.ioSubscriberConnection!.on('reconnecting', () => + this.emit('subscriber:reconnecting', this) + ) + + /** + * On subscriber connection end, we must clear registered + * subscriptions and client event listeners. + */ + this.ioSubscriberConnection!.on('end', async () => { + this.ioConnection.removeAllListeners() + this.emit('subscriber:end', this) + + /** + * Cleanup subscriptions map + */ + this.subscriptions.clear() + this.psubscriptions.clear() + }) + } + + /** + * Gracefully end the redis connection + */ + public async quit() { + await this.ioConnection.quit() + if (this.ioSubscriberConnection) { + await this.ioSubscriberConnection.quit() + } + } + + /** + * Forcefully end the redis connection + */ + public async disconnect() { + await this.ioConnection.disconnect() + if (this.ioSubscriberConnection) { + await this.ioSubscriberConnection.disconnect() + } + } + + /** + * Subscribe to a given channel to receive Redis pub/sub events. A + * new subscriber connection will be created/managed automatically. + */ + public subscribe(channel: string, handler: PubSubChannelHandler | string): void { + /** + * Make the subscriber connection. The method results in a noop when + * subscriber connection already exists. + */ + this.setupSubscriberConnection() + + /** + * Disallow multiple subscriptions to a single channel + */ + if (this.subscriptions.has(channel)) { + throw new Exception( + `"${channel}" channel already has an active subscription`, + 500, + 'E_MULTIPLE_REDIS_SUBSCRIPTIONS' + ) + } + + /** + * If the subscriptions map is empty, it means we have no active subscriptions + * on the given channel, hence we should make one subscription and also set + * the subscription handler. + */ + const connection = this.ioSubscriberConnection as Redis + connection + .subscribe(channel) + .then((count) => { + if (typeof handler === 'string') { + handler = this.resolveIoCBinding(handler) as PubSubChannelHandler + } + this.emit('subscription:ready', count, this) + this.subscriptions.set(channel, handler) + }) + .catch((error) => { + this.emit('subscription:error', error, this) + }) + } + + /** + * Unsubscribe from a channel + */ + public unsubscribe(channel: string) { + this.subscriptions.delete(channel) + return (this.ioSubscriberConnection as Redis).unsubscribe(channel) + } + + /** + * Make redis subscription for a pattern + */ + public psubscribe(pattern: string, handler: PubSubPatternHandler | string): void { + /** + * Make the subscriber connection. The method results in a noop when + * subscriber connection already exists. + */ + this.setupSubscriberConnection() + + /** + * Disallow multiple subscriptions to a single channel + */ + if (this.psubscriptions.has(pattern)) { + throw new Exception( + `${pattern} pattern already has an active subscription`, + 500, + 'E_MULTIPLE_REDIS_PSUBSCRIPTIONS' + ) + } + + /** + * If the subscriptions map is empty, it means we have no active subscriptions + * on the given channel, hence we should make one subscription and also set + * the subscription handler. + */ + const connection = this.ioSubscriberConnection as Redis + + connection + .psubscribe(pattern) + .then((count) => { + if (typeof handler === 'string') { + handler = this.resolveIoCBinding(handler) as PubSubPatternHandler + } + + this.emit('psubscription:ready', count, this) + this.psubscriptions.set(pattern, handler) + }) + .catch((error) => { + this.emit('psubscription:error', error, this) + }) + } + + /** + * Unsubscribe from a given pattern + */ + public punsubscribe(pattern: string) { + this.psubscriptions.delete(pattern) + return (this.ioSubscriberConnection as any).punsubscribe(pattern) + } + + /** + * Returns report for the connection + */ + public async getReport(checkForMemory?: boolean): Promise { + const connection = this.ioConnection as Redis + + /** + * When status === 'connecting' we maximum wait for 3 times and then send + * the report. Which means, if we are unable to connect to redis within + * 3 seconds, we consider the connection unstable. + */ + if (connection.status === 'connecting' && this.deferredReportAttempts < 3 && !this.lastError) { + await sleep() + this.deferredReportAttempts++ + return this.getReport(checkForMemory) + } + + /** + * Returns the status with the last error when connection status + * is not in `connect` state. + */ + if (!['ready', 'connect'].includes(connection.status)) { + return { + connection: this.connectionName, + status: connection.status, + used_memory: 'unknown', + error: this.lastError, + } + } + + try { + /** + * Ping the server for response + */ + await connection.ping() + + /** + * Collect memory when checkForMemory = true + */ + const memory = checkForMemory ? await this.getUsedMemory() : 'unknown' + + return { + connection: this.connectionName, + status: connection.status, + used_memory: memory, + error: null, + } + } catch (error) { + return { + connection: this.connectionName, + status: connection.status, + used_memory: 'unknown', + error, + } + } + } } diff --git a/src/Bindings/Repl.ts b/src/Bindings/Repl.ts index 13cedf5..f1358ed 100644 --- a/src/Bindings/Repl.ts +++ b/src/Bindings/Repl.ts @@ -15,18 +15,18 @@ import { ApplicationContract } from '@ioc:Adonis/Core/Application' * is set to repl. */ export function defineReplBindings(application: ApplicationContract, Repl: ReplContract) { - Repl.addMethod( - 'loadRedis', - (repl) => { - repl.server.context.Redis = application.container.use('Adonis/Addons/Redis') - repl.notify( - `Loaded Redis module. You can access it using the "${repl.colors.underline( - 'Redis' - )}" variable` - ) - }, - { - description: 'Load redis provider and save reference to the "Redis" variable', - } - ) + Repl.addMethod( + 'loadRedis', + (repl) => { + repl.server.context.Redis = application.container.use('Adonis/Addons/Redis') + repl.notify( + `Loaded Redis module. You can access it using the "${repl.colors.underline( + 'Redis' + )}" variable` + ) + }, + { + description: 'Load redis provider and save reference to the "Redis" variable', + } + ) } diff --git a/src/RedisClusterConnection/index.ts b/src/RedisClusterConnection/index.ts index 03b34e7..7a17404 100644 --- a/src/RedisClusterConnection/index.ts +++ b/src/RedisClusterConnection/index.ts @@ -22,37 +22,37 @@ import { AbstractConnection } from '../AbstractConnection' * pub/sub connections by hand, since it handles that internally by itself. */ export class RedisClusterConnection extends AbstractConnection { - constructor( - connectionName: string, - private config: RedisClusterConfig, - application: ApplicationContract - ) { - super(connectionName, application) - this.ioConnection = new Redis.Cluster(this.config.clusters as any[], this.config.clusterOptions) - this.proxyConnectionEvents() - } + constructor( + connectionName: string, + private config: RedisClusterConfig, + application: ApplicationContract + ) { + super(connectionName, application) + this.ioConnection = new Redis.Cluster(this.config.clusters as any[], this.config.clusterOptions) + this.proxyConnectionEvents() + } - /** - * Creates the subscriber connection, the [[AbstractConnection]] will - * invoke this method when first subscription is created. - */ - protected makeSubscriberConnection() { - this.ioSubscriberConnection = new Redis.Cluster( - this.config.clusters as [], - this.config.clusterOptions - ) - } + /** + * Creates the subscriber connection, the [[AbstractConnection]] will + * invoke this method when first subscription is created. + */ + protected makeSubscriberConnection() { + this.ioSubscriberConnection = new Redis.Cluster( + this.config.clusters as [], + this.config.clusterOptions + ) + } - /** - * Returns cluster nodes - */ - public nodes(role?: Redis.NodeRole) { - return this.ioConnection.nodes(role) - } + /** + * Returns cluster nodes + */ + public nodes(role?: Redis.NodeRole) { + return this.ioConnection.nodes(role) + } } ioMethods.forEach((method) => { - RedisClusterConnection.prototype[method] = function redisConnectionProxyFn(...args: any[]) { - return this.ioConnection[method](...args) - } + RedisClusterConnection.prototype[method] = function redisConnectionProxyFn(...args: any[]) { + return this.ioConnection[method](...args) + } }) diff --git a/src/RedisConnection/index.ts b/src/RedisConnection/index.ts index 8ffde11..181bfa0 100644 --- a/src/RedisConnection/index.ts +++ b/src/RedisConnection/index.ts @@ -23,37 +23,37 @@ import { AbstractConnection } from '../AbstractConnection' * by itself. */ export class RedisConnection extends AbstractConnection { - private config: RedisOptions + private config: RedisOptions - constructor( - connectionName: string, - config: RedisConnectionConfig, - application: ApplicationContract - ) { - super(connectionName, application) - this.config = this.normalizeConfig(config) + constructor( + connectionName: string, + config: RedisConnectionConfig, + application: ApplicationContract + ) { + super(connectionName, application) + this.config = this.normalizeConfig(config) - this.ioConnection = new Redis(this.config) - this.proxyConnectionEvents() - } + this.ioConnection = new Redis(this.config) + this.proxyConnectionEvents() + } - /** - * Normalizes config option to be compatible with IORedis - */ - private normalizeConfig(config: RedisConnectionConfig): RedisOptions { - if (typeof config.port === 'string') { - config.port = Number(config.port) - } - return config as RedisOptions - } + /** + * Normalizes config option to be compatible with IORedis + */ + private normalizeConfig(config: RedisConnectionConfig): RedisOptions { + if (typeof config.port === 'string') { + config.port = Number(config.port) + } + return config as RedisOptions + } - /** - * Creates the subscriber connection, the [[AbstractConnection]] will - * invoke this method when first subscription is created. - */ - protected makeSubscriberConnection() { - this.ioSubscriberConnection = new Redis(this.config) - } + /** + * Creates the subscriber connection, the [[AbstractConnection]] will + * invoke this method when first subscription is created. + */ + protected makeSubscriberConnection() { + this.ioSubscriberConnection = new Redis(this.config) + } } /** @@ -61,7 +61,7 @@ export class RedisConnection extends AbstractConnection { * of dynamically adding redis methods to the class prototype. */ ioMethods.forEach((method) => { - RedisConnection.prototype[method] = function redisConnectionProxyFn(...args: any[]) { - return this.ioConnection[method](...args) - } + RedisConnection.prototype[method] = function redisConnectionProxyFn(...args: any[]) { + return this.ioConnection[method](...args) + } }) diff --git a/src/RedisManager/index.ts b/src/RedisManager/index.ts index f85ebd1..22a321a 100644 --- a/src/RedisManager/index.ts +++ b/src/RedisManager/index.ts @@ -14,11 +14,11 @@ import { ApplicationContract } from '@ioc:Adonis/Core/Application' import { Exception, ManagerConfigValidator } from '@poppinss/utils' import { - RedisConfig, - HealthReportNode, - RedisBaseManagerContract, - RedisConnectionContract, - RedisClusterConnectionContract, + RedisConfig, + HealthReportNode, + RedisBaseManagerContract, + RedisConnectionContract, + RedisClusterConnectionContract, } from '@ioc:Adonis/Addons/Redis' import { ioMethods } from '../ioMethods' @@ -30,217 +30,217 @@ import { RedisClusterConnection } from '../RedisClusterConnection' * Redis manager exposes the API to interact with a redis server. */ export class RedisManager implements RedisBaseManagerContract { - /** - * An array of connections with health checks enabled, which means, we always - * create a connection for them, even when they are not used. - */ - private healthCheckConnections: string[] = [] + /** + * An array of connections with health checks enabled, which means, we always + * create a connection for them, even when they are not used. + */ + private healthCheckConnections: string[] = [] - /** - * A copy of live connections. We avoid re-creating a new connection - * everytime and re-use connections. - */ - public activeConnections: { - [key: string]: RedisClusterConnectionContract | RedisConnectionContract - } = {} + /** + * A copy of live connections. We avoid re-creating a new connection + * everytime and re-use connections. + */ + public activeConnections: { + [key: string]: RedisClusterConnectionContract | RedisConnectionContract + } = {} - /** - * A boolean to know whether health checks have been enabled on one - * or more redis connections or not. - */ - public get healthChecksEnabled() { - return this.healthCheckConnections.length > 0 - } + /** + * A boolean to know whether health checks have been enabled on one + * or more redis connections or not. + */ + public get healthChecksEnabled() { + return this.healthCheckConnections.length > 0 + } - /** - * Returns the length of active connections - */ - public get activeConnectionsCount() { - return Object.keys(this.activeConnections).length - } + /** + * Returns the length of active connections + */ + public get activeConnectionsCount() { + return Object.keys(this.activeConnections).length + } - constructor( - private application: ApplicationContract, - private config: RedisConfig, - private emitter: EmitterContract - ) { - this.validateConfig() - this.healthCheckConnections = Object.keys(this.config.connections).filter( - (connection) => this.config.connections[connection].healthCheck - ) - } + constructor( + private application: ApplicationContract, + private config: RedisConfig, + private emitter: EmitterContract + ) { + this.validateConfig() + this.healthCheckConnections = Object.keys(this.config.connections).filter( + (connection) => this.config.connections[connection].healthCheck + ) + } - /** - * Validate config at runtime - */ - private validateConfig() { - const validator = new ManagerConfigValidator(this.config, 'redis', 'config/redis') - validator.validateDefault('connection') - validator.validateList('connections', 'connection') - } + /** + * Validate config at runtime + */ + private validateConfig() { + const validator = new ManagerConfigValidator(this.config, 'redis', 'config/redis') + validator.validateDefault('connection') + validator.validateList('connections', 'connection') + } - /** - * Returns default connnection name - */ - private getDefaultConnection(): string { - return this.config.connection - } + /** + * Returns default connnection name + */ + private getDefaultConnection(): string { + return this.config.connection + } - /** - * Returns an existing connection using it's name or the - * default connection, - */ - private getExistingConnection(name?: string) { - name = name || this.getDefaultConnection() - return this.activeConnections[name] - } + /** + * Returns an existing connection using it's name or the + * default connection, + */ + private getExistingConnection(name?: string) { + name = name || this.getDefaultConnection() + return this.activeConnections[name] + } - /** - * Returns config for a given connection - */ - private getConnectionConfig(name: string) { - return this.config.connections[name] - } + /** + * Returns config for a given connection + */ + private getConnectionConfig(name: string) { + return this.config.connections[name] + } - /** - * Returns redis factory for a given named connection - */ - public connection(name?: string): any { - /** - * Using default connection name when actual name is missing - */ - name = name || this.getDefaultConnection() + /** + * Returns redis factory for a given named connection + */ + public connection(name?: string): any { + /** + * Using default connection name when actual name is missing + */ + name = name || this.getDefaultConnection() - /** - * Return cached connection - */ - if (this.activeConnections[name]) { - return this.activeConnections[name] - } + /** + * Return cached connection + */ + if (this.activeConnections[name]) { + return this.activeConnections[name] + } - const config = this.getConnectionConfig(name) + const config = this.getConnectionConfig(name) - /** - * Raise error if config for the given name is missing - */ - if (!config) { - throw new Exception(`Define config for "${name}" connection inside "config/redis" file`) - } + /** + * Raise error if config for the given name is missing + */ + if (!config) { + throw new Exception(`Define config for "${name}" connection inside "config/redis" file`) + } - /** - * Create connection and store inside the connection pools - * object, so that we can re-use it later - */ - const connection = (this.activeConnections[name] = config.clusters - ? ((new RedisClusterConnection( - name, - config, - this.application - ) as unknown) as RedisClusterConnectionContract) - : ((new RedisConnection( - name, - config, - this.application - ) as unknown) as RedisConnectionContract)) + /** + * Create connection and store inside the connection pools + * object, so that we can re-use it later + */ + const connection = (this.activeConnections[name] = config.clusters + ? ((new RedisClusterConnection( + name, + config, + this.application + ) as unknown) as RedisClusterConnectionContract) + : ((new RedisConnection( + name, + config, + this.application + ) as unknown) as RedisConnectionContract)) - /** - * Forward events to the application event emitter - */ - connection.on('ready', ($connection) => - this.emitter.emit('redis:ready', { connection: $connection }) - ) - connection.on('connect', ($connection) => - this.emitter.emit('redis:connect', { connection: $connection }) - ) - connection.on('error', (error, $connection) => - this.emitter.emit('redis:error', { error, connection: $connection }) - ) - connection.on('node:added', ($connection, node) => - this.emitter.emit('redis:node:added', { node, connection: $connection }) - ) - connection.on('node:removed', (node, $connection) => - this.emitter.emit('redis:node:removed', { node, connection: $connection }) - ) - connection.on('node:error', (error, address, $connection) => - this.emitter.emit('redis:node:error', { error, address, connection: $connection }) - ) + /** + * Forward events to the application event emitter + */ + connection.on('ready', ($connection) => + this.emitter.emit('redis:ready', { connection: $connection }) + ) + connection.on('connect', ($connection) => + this.emitter.emit('redis:connect', { connection: $connection }) + ) + connection.on('error', (error, $connection) => + this.emitter.emit('redis:error', { error, connection: $connection }) + ) + connection.on('node:added', ($connection, node) => + this.emitter.emit('redis:node:added', { node, connection: $connection }) + ) + connection.on('node:removed', (node, $connection) => + this.emitter.emit('redis:node:removed', { node, connection: $connection }) + ) + connection.on('node:error', (error, address, $connection) => + this.emitter.emit('redis:node:error', { error, address, connection: $connection }) + ) - /** - * Stop tracking the connection after it's removed - */ - connection.on('end', ($connection) => { - delete this.activeConnections[$connection.connectionName] - this.emitter.emit('redis:end', { connection: $connection }) - }) + /** + * Stop tracking the connection after it's removed + */ + connection.on('end', ($connection) => { + delete this.activeConnections[$connection.connectionName] + this.emitter.emit('redis:end', { connection: $connection }) + }) - /** - * Return connection - */ - return connection - } + /** + * Return connection + */ + return connection + } - /** - * Quit a named connection or the default connection when no - * name is defined. - */ - public async quit(name?: string): Promise { - const connection = this.getExistingConnection(name) - if (!connection) { - return - } + /** + * Quit a named connection or the default connection when no + * name is defined. + */ + public async quit(name?: string): Promise { + const connection = this.getExistingConnection(name) + if (!connection) { + return + } - return connection.quit() - } + return connection.quit() + } - /** - * Disconnect a named connection or the default connection when no - * name is defined. - */ - public async disconnect(name?: string): Promise { - const connection = this.getExistingConnection(name) - if (!connection) { - return - } + /** + * Disconnect a named connection or the default connection when no + * name is defined. + */ + public async disconnect(name?: string): Promise { + const connection = this.getExistingConnection(name) + if (!connection) { + return + } - return connection.disconnect() - } + return connection.disconnect() + } - /** - * Quit all connections - */ - public async quitAll(): Promise { - await Promise.all(Object.keys(this.activeConnections).map((name) => this.quit(name))) - } + /** + * Quit all connections + */ + public async quitAll(): Promise { + await Promise.all(Object.keys(this.activeConnections).map((name) => this.quit(name))) + } - /** - * Disconnect all connections - */ - public async disconnectAll(): Promise { - await Promise.all(Object.keys(this.activeConnections).map((name) => this.disconnect(name))) - } + /** + * Disconnect all connections + */ + public async disconnectAll(): Promise { + await Promise.all(Object.keys(this.activeConnections).map((name) => this.disconnect(name))) + } - /** - * Returns the report for all connections marked for `healthChecks` - */ - public async report() { - const reports = (await Promise.all( - this.healthCheckConnections.map((connection) => { - return this.connection(connection).getReport(true) - }) - )) as HealthReportNode[] + /** + * Returns the report for all connections marked for `healthChecks` + */ + public async report() { + const reports = (await Promise.all( + this.healthCheckConnections.map((connection) => { + return this.connection(connection).getReport(true) + }) + )) as HealthReportNode[] - const healthy = !reports.find((report) => !!report.error) - return { - displayName: 'Redis', - health: { - healthy, - message: healthy - ? 'All connections are healthy' - : 'One or more redis connections are not healthy', - }, - meta: reports, - } - } + const healthy = !reports.find((report) => !!report.error) + return { + displayName: 'Redis', + health: { + healthy, + message: healthy + ? 'All connections are healthy' + : 'One or more redis connections are not healthy', + }, + meta: reports, + } + } } /** @@ -248,12 +248,12 @@ export class RedisManager implements RedisBaseManagerContract { * of dynamically adding redis methods to the class prototype. */ pubsubMethods.forEach((method) => { - RedisManager.prototype[method] = function redisManagerProxyFn(...args: any[]) { - return this.connection()[method](...args) - } + RedisManager.prototype[method] = function redisManagerProxyFn(...args: any[]) { + return this.connection()[method](...args) + } }) ioMethods.forEach((method) => { - RedisManager.prototype[method] = function redisManagerProxyFn(...args: any[]) { - return this.connection()[method](...args) - } + RedisManager.prototype[method] = function redisManagerProxyFn(...args: any[]) { + return this.connection()[method](...args) + } }) diff --git a/src/ioMethods.ts b/src/ioMethods.ts index e87733c..4050b8a 100644 --- a/src/ioMethods.ts +++ b/src/ioMethods.ts @@ -8,164 +8,164 @@ */ export const ioMethods = [ - 'connect', - 'send_command', - 'bitcount', - 'get', - 'getBuffer', - 'set', - 'setBuffer', - 'callback', - 'setnx', - 'setex', - 'psetex', - 'append', - 'strlen', - 'del', - 'exists', - 'setbit', - 'getbit', - 'setrange', - 'getrange', - 'substr', - 'incr', - 'decr', - 'mget', - 'rpush', - 'lpush', - 'rpushx', - 'lpushx', - 'linsert', - 'rpop', - 'lpop', - 'brpop', - 'blpop', - 'brpoplpush', - 'llen', - 'lindex', - 'lset', - 'lrange', - 'ltrim', - 'lrem', - 'rpoplpush', - 'sadd', - 'srem', - 'smove', - 'sismember', - 'scard', - 'spop', - 'publish', - 'srandmember', - 'sinter', - 'sinterstore', - 'sunion', - 'sunionstore', - 'sdiff', - 'sdiffstore', - 'smembers', - 'zadd', - 'zincrby', - 'zrem', - 'zremrangebyscore', - 'zremrangebyrank', - 'zinterstore', - 'zrange', - 'zrevrange', - 'zrangebyscore', - 'zrevrangebyscore', - 'zcount', - 'zcard', - 'zscore', - 'zrank', - 'zrevrank', - 'hset', - 'hsetBuffer', - 'hsetnx', - 'hget', - 'hgetBuffer', - 'hmget', - 'hincrby', - 'hincrbyfloat', - 'hdel', - 'hlen', - 'hkeys', - 'hvals', - 'hgetall', - 'hexists', - 'incrby', - 'incrbyfloat', - 'decrby', - 'getset', - 'mset', - 'msetnx', - 'randomkey', - 'select', - 'move', - 'rename', - 'renamenx', - 'expire', - 'pexpire', - 'expireat', - 'pexpireat', - 'keys', - 'dbsize', - 'auth', - 'ping', - 'echo', - 'save', - 'bgsave', - 'bgrewriteaof', - 'shutdown', - 'lastsave', - 'type', - 'multi', - 'exec', - 'discard', - 'sync', - 'flushdb', - 'flushall', - 'sort', - 'info', - 'time', - 'monitor', - 'ttl', - 'persist', - 'slaveof', - 'debug', - 'config', - 'watch', - 'unwatch', - 'cluster', - 'restore', - 'migrate', - 'dump', - 'object', - 'client', - 'eval', - 'evalsha', - 'script', - 'scan', - 'sscan', - 'hscan', - 'zscan', - 'pfmerge', - 'pfadd', - 'pfcount', - 'pipeline', - 'scanStream', - 'hscanStream', - 'zscanStream', - 'xack', - 'xadd', - 'xclaim', - 'xdel', - 'xgroup', - 'xinfo', - 'xlen', - 'xpending', - 'xrange', - 'xread', - 'xreadgroup', - 'xrevrange', - 'xtrim', + 'connect', + 'send_command', + 'bitcount', + 'get', + 'getBuffer', + 'set', + 'setBuffer', + 'callback', + 'setnx', + 'setex', + 'psetex', + 'append', + 'strlen', + 'del', + 'exists', + 'setbit', + 'getbit', + 'setrange', + 'getrange', + 'substr', + 'incr', + 'decr', + 'mget', + 'rpush', + 'lpush', + 'rpushx', + 'lpushx', + 'linsert', + 'rpop', + 'lpop', + 'brpop', + 'blpop', + 'brpoplpush', + 'llen', + 'lindex', + 'lset', + 'lrange', + 'ltrim', + 'lrem', + 'rpoplpush', + 'sadd', + 'srem', + 'smove', + 'sismember', + 'scard', + 'spop', + 'publish', + 'srandmember', + 'sinter', + 'sinterstore', + 'sunion', + 'sunionstore', + 'sdiff', + 'sdiffstore', + 'smembers', + 'zadd', + 'zincrby', + 'zrem', + 'zremrangebyscore', + 'zremrangebyrank', + 'zinterstore', + 'zrange', + 'zrevrange', + 'zrangebyscore', + 'zrevrangebyscore', + 'zcount', + 'zcard', + 'zscore', + 'zrank', + 'zrevrank', + 'hset', + 'hsetBuffer', + 'hsetnx', + 'hget', + 'hgetBuffer', + 'hmget', + 'hincrby', + 'hincrbyfloat', + 'hdel', + 'hlen', + 'hkeys', + 'hvals', + 'hgetall', + 'hexists', + 'incrby', + 'incrbyfloat', + 'decrby', + 'getset', + 'mset', + 'msetnx', + 'randomkey', + 'select', + 'move', + 'rename', + 'renamenx', + 'expire', + 'pexpire', + 'expireat', + 'pexpireat', + 'keys', + 'dbsize', + 'auth', + 'ping', + 'echo', + 'save', + 'bgsave', + 'bgrewriteaof', + 'shutdown', + 'lastsave', + 'type', + 'multi', + 'exec', + 'discard', + 'sync', + 'flushdb', + 'flushall', + 'sort', + 'info', + 'time', + 'monitor', + 'ttl', + 'persist', + 'slaveof', + 'debug', + 'config', + 'watch', + 'unwatch', + 'cluster', + 'restore', + 'migrate', + 'dump', + 'object', + 'client', + 'eval', + 'evalsha', + 'script', + 'scan', + 'sscan', + 'hscan', + 'zscan', + 'pfmerge', + 'pfadd', + 'pfcount', + 'pipeline', + 'scanStream', + 'hscanStream', + 'zscanStream', + 'xack', + 'xadd', + 'xclaim', + 'xdel', + 'xgroup', + 'xinfo', + 'xlen', + 'xpending', + 'xrange', + 'xread', + 'xreadgroup', + 'xrevrange', + 'xtrim', ] diff --git a/test/contracts.ts b/test/contracts.ts index b68d896..e992505 100644 --- a/test/contracts.ts +++ b/test/contracts.ts @@ -1,6 +1,6 @@ declare module '@ioc:Adonis/Addons/Redis' { - export interface RedisConnectionsList { - primary: RedisConnectionConfig - cluster: RedisClusterConfig - } + export interface RedisConnectionsList { + primary: RedisConnectionConfig + cluster: RedisClusterConfig + } } diff --git a/test/redis-cluster-factory.spec.ts b/test/redis-cluster-factory.spec.ts index fc8944f..3369345 100644 --- a/test/redis-cluster-factory.spec.ts +++ b/test/redis-cluster-factory.spec.ts @@ -16,205 +16,205 @@ import { RedisClusterConnectionContract } from '@ioc:Adonis/Addons/Redis' import { RedisClusterConnection } from '../src/RedisClusterConnection' const nodes = process.env.REDIS_CLUSTER_PORTS!.split(',').map((port) => { - return { host: process.env.REDIS_HOST!, port: Number(port) } + return { host: process.env.REDIS_HOST!, port: Number(port) } }) test.group('Redis cluster factory', () => { - test('emit ready when connected to redis server', (assert, done) => { - const factory = (new RedisClusterConnection( - 'main', - { - clusters: nodes, - }, - new Application(__dirname, 'web', {}) - ) as unknown) as RedisClusterConnectionContract - - factory.on('ready', async () => { - assert.isTrue(true) - await factory.quit() - done() - }) - }) - - test('emit node connection event', (assert, done) => { - const factory = (new RedisClusterConnection( - 'main', - { - clusters: [{ host: process.env.REDIS_HOST!!, port: 7000 }], - }, - new Application(__dirname, 'web', {}) - ) as unknown) as RedisClusterConnectionContract - - factory.on('node:added', async () => { - assert.isTrue(true) - await factory.quit() - done() - }) - }) - - test('execute redis commands', async (assert) => { - const factory = (new RedisClusterConnection( - 'main', - { - clusters: nodes, - }, - new Application(__dirname, 'web', {}) - ) as unknown) as RedisClusterConnectionContract - - await factory.set('greeting', 'hello world') - const greeting = await factory.get('greeting') - assert.equal(greeting, 'hello world') - - await factory.del('greeting') - await factory.quit() - }) - - test('clean event listeners on quit', async (assert, done) => { - assert.plan(2) - - const factory = (new RedisClusterConnection( - 'main', - { - clusters: nodes, - }, - new Application(__dirname, 'web', {}) - ) as unknown) as RedisClusterConnectionContract - - factory.on('end', () => { - assert.equal(factory.ioConnection.listenerCount('ready'), 0) - assert.equal(factory.ioConnection.listenerCount('end'), 0) - done() - }) - - factory.on('ready', async () => { - await factory.quit() - }) - }) - - test('clean event listeners on disconnect', async (assert, done) => { - assert.plan(2) - - const factory = (new RedisClusterConnection( - 'main', - { - clusters: nodes, - }, - new Application(__dirname, 'web', {}) - ) as unknown) as RedisClusterConnectionContract - - factory.on('end', () => { - assert.equal(factory.ioConnection.listenerCount('ready'), 0) - assert.equal(factory.ioConnection.listenerCount('end'), 0) - done() - }) - - factory.on('ready', async () => { - await factory.disconnect() - }) - }) - - test('get event for connection errors', async (assert, done) => { - assert.plan(2) - - const factory = (new RedisClusterConnection( - 'main', - { - clusters: [{ host: process.env.REDIS_HOST!, port: 5000 }], - }, - new Application(__dirname, 'web', {}) - ) as unknown) as RedisClusterConnectionContract - - factory.on('end', () => { - assert.equal(factory.ioConnection.listenerCount('ready'), 0) - assert.equal(factory.ioConnection.listenerCount('end'), 0) - done() - }) - - factory.on('error', () => {}) - - /** - * `error` event is also emitted - */ - factory.on('node:error', async () => { - await factory.quit() - }) - }) - - test('access cluster nodes', async (assert, done) => { - assert.plan(3) - - const factory = (new RedisClusterConnection( - 'main', - { - clusters: nodes, - }, - new Application(__dirname, 'web', {}) - ) as unknown) as RedisClusterConnectionContract - - factory.on('end', () => { - assert.equal(factory.ioConnection.listenerCount('ready'), 0) - assert.equal(factory.ioConnection.listenerCount('end'), 0) - done() - }) - - factory.on('ready', async () => { - assert.isAbove(factory.nodes().length, 2) // defined in compose file - await factory.quit() - }) - }) - - test('get report for connected connection', async (assert, done) => { - assert.plan(5) - - const factory = (new RedisClusterConnection( - 'main', - { - clusters: nodes, - }, - new Application(__dirname, 'web', {}) - ) as unknown) as RedisClusterConnectionContract - - factory.on('end', () => { - assert.equal(factory.ioConnection.listenerCount('ready'), 0) - assert.equal(factory.ioConnection.listenerCount('end'), 0) - done() - }) - - factory.on('ready', async () => { - const report = await factory.getReport(true) - - assert.equal(report.status, 'ready') - assert.isNull(report.error) - assert.isDefined(report.used_memory) - - await factory.quit() - }) - }) - - test('get report for errored connection', async (assert, done) => { - assert.plan(5) - - const factory = (new RedisClusterConnection( - 'main', - { - clusters: [{ host: process.env.REDIS_HOST!, port: 5000 }], - }, - new Application(__dirname, 'web', {}) - ) as unknown) as RedisClusterConnectionContract - - factory.on('end', () => { - assert.equal(factory.ioConnection.listenerCount('ready'), 0) - assert.equal(factory.ioConnection.listenerCount('end'), 0) - done() - }) - - factory.on('error', async () => { - const report = await factory.getReport(true) - assert.notEqual(report.status, 'ready') - assert.match(report.error.message, /Failed to refresh/) - assert.equal(report.used_memory, 'unknown') - - await factory.quit() - }) - }) + test('emit ready when connected to redis server', (assert, done) => { + const factory = (new RedisClusterConnection( + 'main', + { + clusters: nodes, + }, + new Application(__dirname, 'web', {}) + ) as unknown) as RedisClusterConnectionContract + + factory.on('ready', async () => { + assert.isTrue(true) + await factory.quit() + done() + }) + }) + + test('emit node connection event', (assert, done) => { + const factory = (new RedisClusterConnection( + 'main', + { + clusters: [{ host: process.env.REDIS_HOST!!, port: 7000 }], + }, + new Application(__dirname, 'web', {}) + ) as unknown) as RedisClusterConnectionContract + + factory.on('node:added', async () => { + assert.isTrue(true) + await factory.quit() + done() + }) + }) + + test('execute redis commands', async (assert) => { + const factory = (new RedisClusterConnection( + 'main', + { + clusters: nodes, + }, + new Application(__dirname, 'web', {}) + ) as unknown) as RedisClusterConnectionContract + + await factory.set('greeting', 'hello world') + const greeting = await factory.get('greeting') + assert.equal(greeting, 'hello world') + + await factory.del('greeting') + await factory.quit() + }) + + test('clean event listeners on quit', async (assert, done) => { + assert.plan(2) + + const factory = (new RedisClusterConnection( + 'main', + { + clusters: nodes, + }, + new Application(__dirname, 'web', {}) + ) as unknown) as RedisClusterConnectionContract + + factory.on('end', () => { + assert.equal(factory.ioConnection.listenerCount('ready'), 0) + assert.equal(factory.ioConnection.listenerCount('end'), 0) + done() + }) + + factory.on('ready', async () => { + await factory.quit() + }) + }) + + test('clean event listeners on disconnect', async (assert, done) => { + assert.plan(2) + + const factory = (new RedisClusterConnection( + 'main', + { + clusters: nodes, + }, + new Application(__dirname, 'web', {}) + ) as unknown) as RedisClusterConnectionContract + + factory.on('end', () => { + assert.equal(factory.ioConnection.listenerCount('ready'), 0) + assert.equal(factory.ioConnection.listenerCount('end'), 0) + done() + }) + + factory.on('ready', async () => { + await factory.disconnect() + }) + }) + + test('get event for connection errors', async (assert, done) => { + assert.plan(2) + + const factory = (new RedisClusterConnection( + 'main', + { + clusters: [{ host: process.env.REDIS_HOST!, port: 5000 }], + }, + new Application(__dirname, 'web', {}) + ) as unknown) as RedisClusterConnectionContract + + factory.on('end', () => { + assert.equal(factory.ioConnection.listenerCount('ready'), 0) + assert.equal(factory.ioConnection.listenerCount('end'), 0) + done() + }) + + factory.on('error', () => {}) + + /** + * `error` event is also emitted + */ + factory.on('node:error', async () => { + await factory.quit() + }) + }) + + test('access cluster nodes', async (assert, done) => { + assert.plan(3) + + const factory = (new RedisClusterConnection( + 'main', + { + clusters: nodes, + }, + new Application(__dirname, 'web', {}) + ) as unknown) as RedisClusterConnectionContract + + factory.on('end', () => { + assert.equal(factory.ioConnection.listenerCount('ready'), 0) + assert.equal(factory.ioConnection.listenerCount('end'), 0) + done() + }) + + factory.on('ready', async () => { + assert.isAbove(factory.nodes().length, 2) // defined in compose file + await factory.quit() + }) + }) + + test('get report for connected connection', async (assert, done) => { + assert.plan(5) + + const factory = (new RedisClusterConnection( + 'main', + { + clusters: nodes, + }, + new Application(__dirname, 'web', {}) + ) as unknown) as RedisClusterConnectionContract + + factory.on('end', () => { + assert.equal(factory.ioConnection.listenerCount('ready'), 0) + assert.equal(factory.ioConnection.listenerCount('end'), 0) + done() + }) + + factory.on('ready', async () => { + const report = await factory.getReport(true) + + assert.equal(report.status, 'ready') + assert.isNull(report.error) + assert.isDefined(report.used_memory) + + await factory.quit() + }) + }) + + test('get report for errored connection', async (assert, done) => { + assert.plan(5) + + const factory = (new RedisClusterConnection( + 'main', + { + clusters: [{ host: process.env.REDIS_HOST!, port: 5000 }], + }, + new Application(__dirname, 'web', {}) + ) as unknown) as RedisClusterConnectionContract + + factory.on('end', () => { + assert.equal(factory.ioConnection.listenerCount('ready'), 0) + assert.equal(factory.ioConnection.listenerCount('end'), 0) + done() + }) + + factory.on('error', async () => { + const report = await factory.getReport(true) + assert.notEqual(report.status, 'ready') + assert.match(report.error.message, /Failed to refresh/) + assert.equal(report.used_memory, 'unknown') + + await factory.quit() + }) + }) }) diff --git a/test/redis-connection.spec.ts b/test/redis-connection.spec.ts index 0b328af..8139aec 100644 --- a/test/redis-connection.spec.ts +++ b/test/redis-connection.spec.ts @@ -16,475 +16,475 @@ import { RedisConnectionContract } from '@ioc:Adonis/Addons/Redis' import { RedisConnection } from '../src/RedisConnection' test.group('Redis factory', () => { - test('emit ready when connected to redis server', (assert, done) => { - const factory = (new RedisConnection( - 'main', - { - host: process.env.REDIS_HOST, - port: Number(process.env.REDIS_PORT), - }, - new Application(__dirname, 'web', {}) - ) as unknown) as RedisConnectionContract - - factory.on('error', async (error) => { - console.log(error) - }) - - factory.on('ready', async () => { - assert.isTrue(true) - await factory.quit() - done() - }) - }) - - test('execute redis commands', async (assert) => { - const factory = (new RedisConnection( - 'main', - { - host: process.env.REDIS_HOST, - port: Number(process.env.REDIS_PORT), - }, - new Application(__dirname, 'web', {}) - ) as unknown) as RedisConnectionContract - - await factory.set('greeting', 'hello world') - - const greeting = await factory.get('greeting') - assert.equal(greeting, 'hello world') - - await factory.del('greeting') - await factory.quit() - }) - - test('clean event listeners on quit', async (assert, done) => { - const factory = (new RedisConnection( - 'main', - { - host: process.env.REDIS_HOST, - port: Number(process.env.REDIS_PORT), - }, - new Application(__dirname, 'web', {}) - ) as unknown) as RedisConnectionContract - - factory.on('end', () => { - assert.equal(factory.ioConnection.listenerCount('ready'), 0) - assert.equal(factory.ioConnection.listenerCount('end'), 0) - done() - }) - - factory.on('ready', async () => { - await factory.quit() - }) - }) - - test('clean event listeners on disconnect', async (assert, done) => { - const factory = (new RedisConnection( - 'main', - { - host: process.env.REDIS_HOST, - port: Number(process.env.REDIS_PORT), - }, - new Application(__dirname, 'web', {}) - ) as unknown) as RedisConnectionContract - - factory.on('end', () => { - assert.equal(factory.ioConnection.listenerCount('ready'), 0) - assert.equal(factory.ioConnection.listenerCount('end'), 0) - done() - }) - - factory.on('ready', async () => { - await factory.quit() - }) - }) - - test('get event for connection errors', async (assert, done) => { - const factory = (new RedisConnection( - 'main', - { port: 4444 }, - new Application(__dirname, 'web', {}) - ) as unknown) as RedisConnectionContract - - factory.on('end', () => { - assert.equal(factory.ioConnection.listenerCount('ready'), 0) - assert.equal(factory.ioConnection.listenerCount('end'), 0) - done() - }) - - factory.on('error', async (error) => { - assert.equal(error.code, 'ECONNREFUSED') - assert.equal(error.port, 4444) - await factory.quit() - }) - }) - - test('get report for connected connection', async (assert, done) => { - assert.plan(5) - - const factory = (new RedisConnection( - 'main', - { - host: process.env.REDIS_HOST, - port: Number(process.env.REDIS_PORT), - }, - new Application(__dirname, 'web', {}) - ) as unknown) as RedisConnectionContract - - factory.on('end', () => { - assert.equal(factory.ioConnection.listenerCount('ready'), 0) - assert.equal(factory.ioConnection.listenerCount('end'), 0) - done() - }) - - factory.on('ready', async () => { - const report = await factory.getReport(true) - - assert.equal(report.status, 'ready') - assert.isNull(report.error) - assert.isDefined(report.used_memory) - - await factory.quit() - }) - }) - - test('get report for errored connection', async (assert, done) => { - assert.plan(5) - - const factory = (new RedisConnection( - 'main', - { - host: process.env.REDIS_HOST, - port: 4444, - }, - new Application(__dirname, 'web', {}) - ) as unknown) as RedisConnectionContract - - factory.on('end', () => { - assert.equal(factory.ioConnection.listenerCount('ready'), 0) - assert.equal(factory.ioConnection.listenerCount('end'), 0) - done() - }) - - factory.on('error', async () => { - const report = await factory.getReport(true) - - assert.notEqual(report.status, 'ready') - assert.equal(report.error.code, 'ECONNREFUSED') - assert.equal(report.used_memory, 'unknown') - - await factory.quit() - }) - }) + test('emit ready when connected to redis server', (assert, done) => { + const factory = (new RedisConnection( + 'main', + { + host: process.env.REDIS_HOST, + port: Number(process.env.REDIS_PORT), + }, + new Application(__dirname, 'web', {}) + ) as unknown) as RedisConnectionContract + + factory.on('error', async (error) => { + console.log(error) + }) + + factory.on('ready', async () => { + assert.isTrue(true) + await factory.quit() + done() + }) + }) + + test('execute redis commands', async (assert) => { + const factory = (new RedisConnection( + 'main', + { + host: process.env.REDIS_HOST, + port: Number(process.env.REDIS_PORT), + }, + new Application(__dirname, 'web', {}) + ) as unknown) as RedisConnectionContract + + await factory.set('greeting', 'hello world') + + const greeting = await factory.get('greeting') + assert.equal(greeting, 'hello world') + + await factory.del('greeting') + await factory.quit() + }) + + test('clean event listeners on quit', async (assert, done) => { + const factory = (new RedisConnection( + 'main', + { + host: process.env.REDIS_HOST, + port: Number(process.env.REDIS_PORT), + }, + new Application(__dirname, 'web', {}) + ) as unknown) as RedisConnectionContract + + factory.on('end', () => { + assert.equal(factory.ioConnection.listenerCount('ready'), 0) + assert.equal(factory.ioConnection.listenerCount('end'), 0) + done() + }) + + factory.on('ready', async () => { + await factory.quit() + }) + }) + + test('clean event listeners on disconnect', async (assert, done) => { + const factory = (new RedisConnection( + 'main', + { + host: process.env.REDIS_HOST, + port: Number(process.env.REDIS_PORT), + }, + new Application(__dirname, 'web', {}) + ) as unknown) as RedisConnectionContract + + factory.on('end', () => { + assert.equal(factory.ioConnection.listenerCount('ready'), 0) + assert.equal(factory.ioConnection.listenerCount('end'), 0) + done() + }) + + factory.on('ready', async () => { + await factory.quit() + }) + }) + + test('get event for connection errors', async (assert, done) => { + const factory = (new RedisConnection( + 'main', + { port: 4444 }, + new Application(__dirname, 'web', {}) + ) as unknown) as RedisConnectionContract + + factory.on('end', () => { + assert.equal(factory.ioConnection.listenerCount('ready'), 0) + assert.equal(factory.ioConnection.listenerCount('end'), 0) + done() + }) + + factory.on('error', async (error) => { + assert.equal(error.code, 'ECONNREFUSED') + assert.equal(error.port, 4444) + await factory.quit() + }) + }) + + test('get report for connected connection', async (assert, done) => { + assert.plan(5) + + const factory = (new RedisConnection( + 'main', + { + host: process.env.REDIS_HOST, + port: Number(process.env.REDIS_PORT), + }, + new Application(__dirname, 'web', {}) + ) as unknown) as RedisConnectionContract + + factory.on('end', () => { + assert.equal(factory.ioConnection.listenerCount('ready'), 0) + assert.equal(factory.ioConnection.listenerCount('end'), 0) + done() + }) + + factory.on('ready', async () => { + const report = await factory.getReport(true) + + assert.equal(report.status, 'ready') + assert.isNull(report.error) + assert.isDefined(report.used_memory) + + await factory.quit() + }) + }) + + test('get report for errored connection', async (assert, done) => { + assert.plan(5) + + const factory = (new RedisConnection( + 'main', + { + host: process.env.REDIS_HOST, + port: 4444, + }, + new Application(__dirname, 'web', {}) + ) as unknown) as RedisConnectionContract + + factory.on('end', () => { + assert.equal(factory.ioConnection.listenerCount('ready'), 0) + assert.equal(factory.ioConnection.listenerCount('end'), 0) + done() + }) + + factory.on('error', async () => { + const report = await factory.getReport(true) + + assert.notEqual(report.status, 'ready') + assert.equal(report.error.code, 'ECONNREFUSED') + assert.equal(report.used_memory, 'unknown') + + await factory.quit() + }) + }) }) test.group('Redis factory - Subscribe', () => { - test('emit subscriber events when subscriber connection is created', async (_assert, done) => { - const factory = (new RedisConnection( - 'main', - { - host: process.env.REDIS_HOST, - port: Number(process.env.REDIS_PORT), - }, - new Application(__dirname, 'web', {}) - ) as unknown) as RedisConnectionContract - - factory.on('subscriber:ready', async () => { - await factory.quit() - done() - }) - - factory.subscribe('news', () => {}) - }) - - test('emit subscription event when subscription is created', async (assert, done) => { - const factory = (new RedisConnection( - 'main', - { - host: process.env.REDIS_HOST, - port: Number(process.env.REDIS_PORT), - }, - new Application(__dirname, 'web', {}) - ) as unknown) as RedisConnectionContract - - factory.on('subscription:ready', async (count) => { - assert.equal(count, 1) - await factory.quit() - done() - }) - - factory.subscribe('news', () => {}) - }) - - test('make multiple subscriptions to different channels', async (assert, done) => { - const factory = (new RedisConnection( - 'main', - { - host: process.env.REDIS_HOST, - port: Number(process.env.REDIS_PORT), - }, - new Application(__dirname, 'web', {}) - ) as unknown) as RedisConnectionContract - let invokedCounts = 0 - - factory.on('subscription:ready', async (count) => { - invokedCounts++ - - if (invokedCounts === 2) { - assert.equal(count, 2) - await factory.quit() - done() - } else { - assert.equal(count, 1) - } - }) - - factory.subscribe('news', () => {}) - factory.subscribe('sports', () => {}) - }) - - test('publish messages', async (assert, done) => { - const factory = (new RedisConnection( - 'main', - { - host: process.env.REDIS_HOST, - port: Number(process.env.REDIS_PORT), - }, - new Application(__dirname, 'web', {}) - ) as unknown) as RedisConnectionContract - factory.subscribe('news', async (message) => { - assert.equal(message, 'breaking news at 9') - await factory.quit() - done() - }) - - factory.on('subscription:ready', () => { - factory.publish('news', 'breaking news at 9') - }) - }) - - test('publish messages to multiple channels', async (assert, done) => { - const factory = (new RedisConnection( - 'main', - { - host: process.env.REDIS_HOST, - port: Number(process.env.REDIS_PORT), - }, - new Application(__dirname, 'web', {}) - ) as unknown) as RedisConnectionContract - - factory.on('subscription:ready', (count) => { - if (count === 1) { - factory.publish('news', 'breaking news at 9') - } - }) - - factory.subscribe('news', (message) => { - assert.equal(message, 'breaking news at 9') - factory.publish('sports', 'india won the cup') - }) - - factory.subscribe('sports', async (message) => { - assert.equal(message, 'india won the cup') - await factory.quit() - done() - }) - }) - - test('unsubscribe from a channel', async (assert, done) => { - const factory = (new RedisConnection( - 'main', - { - host: process.env.REDIS_HOST, - port: Number(process.env.REDIS_PORT), - }, - new Application(__dirname, 'web', {}) - ) as unknown) as RedisConnectionContract - - factory.on('subscription:ready', () => { - factory.publish('news', 'breaking news at 9') - }) - - factory.subscribe('news', (message) => { - assert.equal(message, 'breaking news at 9') - factory.unsubscribe('news') - - factory.publish('news', 'breaking news at 9', (_error, count) => { - assert.equal(count, 0) - done() - }) - }) - }) + test('emit subscriber events when subscriber connection is created', async (_assert, done) => { + const factory = (new RedisConnection( + 'main', + { + host: process.env.REDIS_HOST, + port: Number(process.env.REDIS_PORT), + }, + new Application(__dirname, 'web', {}) + ) as unknown) as RedisConnectionContract + + factory.on('subscriber:ready', async () => { + await factory.quit() + done() + }) + + factory.subscribe('news', () => {}) + }) + + test('emit subscription event when subscription is created', async (assert, done) => { + const factory = (new RedisConnection( + 'main', + { + host: process.env.REDIS_HOST, + port: Number(process.env.REDIS_PORT), + }, + new Application(__dirname, 'web', {}) + ) as unknown) as RedisConnectionContract + + factory.on('subscription:ready', async (count) => { + assert.equal(count, 1) + await factory.quit() + done() + }) + + factory.subscribe('news', () => {}) + }) + + test('make multiple subscriptions to different channels', async (assert, done) => { + const factory = (new RedisConnection( + 'main', + { + host: process.env.REDIS_HOST, + port: Number(process.env.REDIS_PORT), + }, + new Application(__dirname, 'web', {}) + ) as unknown) as RedisConnectionContract + let invokedCounts = 0 + + factory.on('subscription:ready', async (count) => { + invokedCounts++ + + if (invokedCounts === 2) { + assert.equal(count, 2) + await factory.quit() + done() + } else { + assert.equal(count, 1) + } + }) + + factory.subscribe('news', () => {}) + factory.subscribe('sports', () => {}) + }) + + test('publish messages', async (assert, done) => { + const factory = (new RedisConnection( + 'main', + { + host: process.env.REDIS_HOST, + port: Number(process.env.REDIS_PORT), + }, + new Application(__dirname, 'web', {}) + ) as unknown) as RedisConnectionContract + factory.subscribe('news', async (message) => { + assert.equal(message, 'breaking news at 9') + await factory.quit() + done() + }) + + factory.on('subscription:ready', () => { + factory.publish('news', 'breaking news at 9') + }) + }) + + test('publish messages to multiple channels', async (assert, done) => { + const factory = (new RedisConnection( + 'main', + { + host: process.env.REDIS_HOST, + port: Number(process.env.REDIS_PORT), + }, + new Application(__dirname, 'web', {}) + ) as unknown) as RedisConnectionContract + + factory.on('subscription:ready', (count) => { + if (count === 1) { + factory.publish('news', 'breaking news at 9') + } + }) + + factory.subscribe('news', (message) => { + assert.equal(message, 'breaking news at 9') + factory.publish('sports', 'india won the cup') + }) + + factory.subscribe('sports', async (message) => { + assert.equal(message, 'india won the cup') + await factory.quit() + done() + }) + }) + + test('unsubscribe from a channel', async (assert, done) => { + const factory = (new RedisConnection( + 'main', + { + host: process.env.REDIS_HOST, + port: Number(process.env.REDIS_PORT), + }, + new Application(__dirname, 'web', {}) + ) as unknown) as RedisConnectionContract + + factory.on('subscription:ready', () => { + factory.publish('news', 'breaking news at 9') + }) + + factory.subscribe('news', (message) => { + assert.equal(message, 'breaking news at 9') + factory.unsubscribe('news') + + factory.publish('news', 'breaking news at 9', (_error, count) => { + assert.equal(count, 0) + done() + }) + }) + }) }) test.group('Redis factory - PSubscribe', () => { - test('emit subscriber events when subscriber connection is created', async (_assert, done) => { - const factory = (new RedisConnection( - 'main', - { - host: process.env.REDIS_HOST, - port: Number(process.env.REDIS_PORT), - }, - new Application(__dirname, 'web', {}) - ) as unknown) as RedisConnectionContract - factory.on('subscriber:ready', async () => { - await factory.quit() - done() - }) - - factory.psubscribe('news:*', () => {}) - }) - - test('emit subscription event when subscription is created', async (assert, done) => { - const factory = (new RedisConnection( - 'main', - { - host: process.env.REDIS_HOST, - port: Number(process.env.REDIS_PORT), - }, - new Application(__dirname, 'web', {}) - ) as unknown) as RedisConnectionContract - factory.on('psubscription:ready', async (count) => { - assert.equal(count, 1) - await factory.quit() - done() - }) - - factory.psubscribe('news:*', () => {}) - }) - - test('make multiple subscriptions to different patterns', async (assert, done) => { - const factory = (new RedisConnection( - 'main', - { - host: process.env.REDIS_HOST, - port: Number(process.env.REDIS_PORT), - }, - new Application(__dirname, 'web', {}) - ) as unknown) as RedisConnectionContract - let invokedCounts = 0 - - factory.on('psubscription:ready', async (count) => { - invokedCounts++ - - if (invokedCounts === 2) { - assert.equal(count, 2) - await factory.quit() - done() - } else { - assert.equal(count, 1) - } - }) - - factory.psubscribe('news:*', () => {}) - factory.psubscribe('sports:*', () => {}) - }) - - test('publish messages', async (assert, done) => { - const factory = (new RedisConnection( - 'main', - { - host: process.env.REDIS_HOST, - port: Number(process.env.REDIS_PORT), - }, - new Application(__dirname, 'web', {}) - ) as unknown) as RedisConnectionContract - factory.psubscribe('news:*', async (channel, message) => { - assert.equal(channel, 'news:prime') - assert.equal(message, 'breaking news at 9') - await factory.quit() - done() - }) - - factory.on('psubscription:ready', () => { - factory.publish('news:prime', 'breaking news at 9') - }) - }) - - test('publish messages to multiple channels', async (assert, done) => { - assert.plan(2) - const factory = (new RedisConnection( - 'main', - { - host: process.env.REDIS_HOST, - port: Number(process.env.REDIS_PORT), - }, - new Application(__dirname, 'web', {}) - ) as unknown) as RedisConnectionContract - - factory.on('psubscription:ready', (count) => { - if (count === 1) { - factory.publish('news:prime', 'breaking news at 9') - } - }) - - factory.psubscribe('news:*', async (channel, message) => { - if (channel === 'news:prime') { - assert.equal(message, 'breaking news at 9') - factory.publish('news:breakfast', 'celebrating marathon') - } - - if (channel === 'news:breakfast') { - assert.equal(message, 'celebrating marathon') - await factory.quit() - done() - } - }) - }) - - test('unsubscribe from a pattern', async (assert, done) => { - const factory = (new RedisConnection( - 'main', - { - host: process.env.REDIS_HOST, - port: Number(process.env.REDIS_PORT), - }, - new Application(__dirname, 'web', {}) - ) as unknown) as RedisConnectionContract - - factory.on('psubscription:ready', () => { - factory.publish('news:prime', 'breaking news at 9') - }) - - factory.psubscribe('news:*', (channel, message) => { - assert.equal(channel, 'news:prime') - assert.equal(message, 'breaking news at 9') - factory.punsubscribe('news:*') - - factory.publish('news:prime', 'breaking news at 9', (_error, count) => { - assert.equal(count, 0) - done() - }) - }) - }) - - test('bind IoC container binding as subscriber', async (assert, done) => { - const app = new Application(__dirname, 'web', {}) - const factory = (new RedisConnection( - 'main', - { - host: process.env.REDIS_HOST, - port: Number(process.env.REDIS_PORT), - }, - app - ) as unknown) as RedisConnectionContract - - class RedisListeners { - public async onNews(channel: string, message: string) { - assert.equal(channel, 'news:prime') - assert.equal(message, 'breaking news at 9') - await factory.quit() - done() - } - } - - app.container.bind('App/Listeners/RedisListeners', () => { - return new RedisListeners() - }) - - factory.psubscribe('news:*', 'RedisListeners.onNews') - - factory.on('psubscription:ready', () => { - factory.publish('news:prime', 'breaking news at 9') - }) - }) + test('emit subscriber events when subscriber connection is created', async (_assert, done) => { + const factory = (new RedisConnection( + 'main', + { + host: process.env.REDIS_HOST, + port: Number(process.env.REDIS_PORT), + }, + new Application(__dirname, 'web', {}) + ) as unknown) as RedisConnectionContract + factory.on('subscriber:ready', async () => { + await factory.quit() + done() + }) + + factory.psubscribe('news:*', () => {}) + }) + + test('emit subscription event when subscription is created', async (assert, done) => { + const factory = (new RedisConnection( + 'main', + { + host: process.env.REDIS_HOST, + port: Number(process.env.REDIS_PORT), + }, + new Application(__dirname, 'web', {}) + ) as unknown) as RedisConnectionContract + factory.on('psubscription:ready', async (count) => { + assert.equal(count, 1) + await factory.quit() + done() + }) + + factory.psubscribe('news:*', () => {}) + }) + + test('make multiple subscriptions to different patterns', async (assert, done) => { + const factory = (new RedisConnection( + 'main', + { + host: process.env.REDIS_HOST, + port: Number(process.env.REDIS_PORT), + }, + new Application(__dirname, 'web', {}) + ) as unknown) as RedisConnectionContract + let invokedCounts = 0 + + factory.on('psubscription:ready', async (count) => { + invokedCounts++ + + if (invokedCounts === 2) { + assert.equal(count, 2) + await factory.quit() + done() + } else { + assert.equal(count, 1) + } + }) + + factory.psubscribe('news:*', () => {}) + factory.psubscribe('sports:*', () => {}) + }) + + test('publish messages', async (assert, done) => { + const factory = (new RedisConnection( + 'main', + { + host: process.env.REDIS_HOST, + port: Number(process.env.REDIS_PORT), + }, + new Application(__dirname, 'web', {}) + ) as unknown) as RedisConnectionContract + factory.psubscribe('news:*', async (channel, message) => { + assert.equal(channel, 'news:prime') + assert.equal(message, 'breaking news at 9') + await factory.quit() + done() + }) + + factory.on('psubscription:ready', () => { + factory.publish('news:prime', 'breaking news at 9') + }) + }) + + test('publish messages to multiple channels', async (assert, done) => { + assert.plan(2) + const factory = (new RedisConnection( + 'main', + { + host: process.env.REDIS_HOST, + port: Number(process.env.REDIS_PORT), + }, + new Application(__dirname, 'web', {}) + ) as unknown) as RedisConnectionContract + + factory.on('psubscription:ready', (count) => { + if (count === 1) { + factory.publish('news:prime', 'breaking news at 9') + } + }) + + factory.psubscribe('news:*', async (channel, message) => { + if (channel === 'news:prime') { + assert.equal(message, 'breaking news at 9') + factory.publish('news:breakfast', 'celebrating marathon') + } + + if (channel === 'news:breakfast') { + assert.equal(message, 'celebrating marathon') + await factory.quit() + done() + } + }) + }) + + test('unsubscribe from a pattern', async (assert, done) => { + const factory = (new RedisConnection( + 'main', + { + host: process.env.REDIS_HOST, + port: Number(process.env.REDIS_PORT), + }, + new Application(__dirname, 'web', {}) + ) as unknown) as RedisConnectionContract + + factory.on('psubscription:ready', () => { + factory.publish('news:prime', 'breaking news at 9') + }) + + factory.psubscribe('news:*', (channel, message) => { + assert.equal(channel, 'news:prime') + assert.equal(message, 'breaking news at 9') + factory.punsubscribe('news:*') + + factory.publish('news:prime', 'breaking news at 9', (_error, count) => { + assert.equal(count, 0) + done() + }) + }) + }) + + test('bind IoC container binding as subscriber', async (assert, done) => { + const app = new Application(__dirname, 'web', {}) + const factory = (new RedisConnection( + 'main', + { + host: process.env.REDIS_HOST, + port: Number(process.env.REDIS_PORT), + }, + app + ) as unknown) as RedisConnectionContract + + class RedisListeners { + public async onNews(channel: string, message: string) { + assert.equal(channel, 'news:prime') + assert.equal(message, 'breaking news at 9') + await factory.quit() + done() + } + } + + app.container.bind('App/Listeners/RedisListeners', () => { + return new RedisListeners() + }) + + factory.psubscribe('news:*', 'RedisListeners.onNews') + + factory.on('psubscription:ready', () => { + factory.publish('news:prime', 'breaking news at 9') + }) + }) }) diff --git a/test/redis-provider.spec.ts b/test/redis-provider.spec.ts index 50adfce..59ea7d9 100644 --- a/test/redis-provider.spec.ts +++ b/test/redis-provider.spec.ts @@ -17,141 +17,141 @@ import { RedisManager } from '../src/RedisManager' const fs = new Filesystem(join(__dirname, 'app')) async function setup(environment: 'web' | 'repl', redisConfig: any) { - await fs.add('.env', '') - await fs.add( - 'config/app.ts', - ` + await fs.add('.env', '') + await fs.add( + 'config/app.ts', + ` export const appKey = 'averylong32charsrandomsecretkey', export const http = { cookie: {}, trustProxy: () => true, } ` - ) + ) - await fs.add( - 'config/redis.ts', - ` + await fs.add( + 'config/redis.ts', + ` const redisConfig = ${JSON.stringify(redisConfig, null, 2)} export default redisConfig ` - ) + ) - const app = new Application(fs.basePath, environment, { - providers: ['@adonisjs/core', '@adonisjs/repl', '../../providers/RedisProvider'], - }) + const app = new Application(fs.basePath, environment, { + providers: ['@adonisjs/core', '@adonisjs/repl', '../../providers/RedisProvider'], + }) - await app.setup() - await app.registerProviders() - await app.bootProviders() + await app.setup() + await app.registerProviders() + await app.bootProviders() - return app + return app } test.group('Redis Provider', (group) => { - group.afterEach(async () => { - await fs.cleanup() - }) - - test('register redis provider', async (assert) => { - const app = await setup('web', { - connection: 'local', - connections: { - local: {}, - }, - }) - - assert.instanceOf(app.container.use('Adonis/Addons/Redis'), RedisManager) - assert.deepEqual(app.container.use('Adonis/Addons/Redis')['application'], app) - assert.deepEqual( - app.container.use('Adonis/Addons/Redis'), - app.container.use('Adonis/Addons/Redis') - ) - }) - - test('raise error when config is missing', async (assert) => { - assert.plan(1) - - try { - await setup('web', {}) - } catch (error) { - assert.equal( - error.message, - 'Invalid "redis" config. Missing value for "connection". Make sure to set it inside the "config/redis" file' - ) - } - }) - - test('raise error when primary connection is not defined', async (assert) => { - assert.plan(1) - - try { - await setup('web', {}) - } catch (error) { - assert.equal( - error.message, - 'Invalid "redis" config. Missing value for "connection". Make sure to set it inside the "config/redis" file' - ) - } - }) - - test('raise error when connections are not defined', async (assert) => { - assert.plan(1) - - try { - await setup('web', { - connection: 'local', - }) - } catch (error) { - assert.equal( - error.message, - 'Invalid "redis" config. Missing value for "connections". Make sure to set it inside the "config/redis" file' - ) - } - }) - - test('raise error when primary connection is not defined in the connections list', async (assert) => { - assert.plan(1) - - try { - await setup('web', { - connection: 'local', - connections: {}, - }) - } catch (error) { - assert.equal( - error.message, - 'Invalid "redis" config. "local" is not defined inside "connections". Make sure to set it inside the "config/redis" file' - ) - } - }) - - test('define repl bindings', async (assert) => { - const app = await setup('repl', { - connection: 'local', - connections: { - local: {}, - }, - }) - - assert.property(app.container.use('Adonis/Addons/Repl')['customMethods'], 'loadRedis') - assert.isFunction(app.container.use('Adonis/Addons/Repl')['customMethods']['loadRedis'].handler) - }) - - test('define health checks', async (assert) => { - const app = await setup('web', { - connection: 'local', - connections: { - local: { - healthCheck: true, - }, - }, - }) - - assert.property(app.container.use('Adonis/Core/HealthCheck')['healthCheckers'], 'redis') - assert.equal( - app.container.use('Adonis/Core/HealthCheck')['healthCheckers'].redis, - 'Adonis/Addons/Redis' - ) - }) + group.afterEach(async () => { + await fs.cleanup() + }) + + test('register redis provider', async (assert) => { + const app = await setup('web', { + connection: 'local', + connections: { + local: {}, + }, + }) + + assert.instanceOf(app.container.use('Adonis/Addons/Redis'), RedisManager) + assert.deepEqual(app.container.use('Adonis/Addons/Redis')['application'], app) + assert.deepEqual( + app.container.use('Adonis/Addons/Redis'), + app.container.use('Adonis/Addons/Redis') + ) + }) + + test('raise error when config is missing', async (assert) => { + assert.plan(1) + + try { + await setup('web', {}) + } catch (error) { + assert.equal( + error.message, + 'Invalid "redis" config. Missing value for "connection". Make sure to set it inside the "config/redis" file' + ) + } + }) + + test('raise error when primary connection is not defined', async (assert) => { + assert.plan(1) + + try { + await setup('web', {}) + } catch (error) { + assert.equal( + error.message, + 'Invalid "redis" config. Missing value for "connection". Make sure to set it inside the "config/redis" file' + ) + } + }) + + test('raise error when connections are not defined', async (assert) => { + assert.plan(1) + + try { + await setup('web', { + connection: 'local', + }) + } catch (error) { + assert.equal( + error.message, + 'Invalid "redis" config. Missing value for "connections". Make sure to set it inside the "config/redis" file' + ) + } + }) + + test('raise error when primary connection is not defined in the connections list', async (assert) => { + assert.plan(1) + + try { + await setup('web', { + connection: 'local', + connections: {}, + }) + } catch (error) { + assert.equal( + error.message, + 'Invalid "redis" config. "local" is not defined inside "connections". Make sure to set it inside the "config/redis" file' + ) + } + }) + + test('define repl bindings', async (assert) => { + const app = await setup('repl', { + connection: 'local', + connections: { + local: {}, + }, + }) + + assert.property(app.container.use('Adonis/Addons/Repl')['customMethods'], 'loadRedis') + assert.isFunction(app.container.use('Adonis/Addons/Repl')['customMethods']['loadRedis'].handler) + }) + + test('define health checks', async (assert) => { + const app = await setup('web', { + connection: 'local', + connections: { + local: { + healthCheck: true, + }, + }, + }) + + assert.property(app.container.use('Adonis/Core/HealthCheck')['healthCheckers'], 'redis') + assert.equal( + app.container.use('Adonis/Core/HealthCheck')['healthCheckers'].redis, + 'Adonis/Addons/Redis' + ) + }) }) diff --git a/test/redis.spec.ts b/test/redis.spec.ts index fd21990..08a9e70 100644 --- a/test/redis.spec.ts +++ b/test/redis.spec.ts @@ -16,232 +16,232 @@ import { Application, Emitter } from '@adonisjs/core/build/standalone' import { RedisManager } from '../src/RedisManager' const clusterNodes = process.env.REDIS_CLUSTER_PORTS!.split(',').map((port) => { - return { host: process.env.REDIS_HOST!, port: Number(port) } + return { host: process.env.REDIS_HOST!, port: Number(port) } }) test.group('Redis Manager', () => { - test('run redis commands using default connection', async (assert) => { - const app = new Application(__dirname, 'web', {}) - const redis = (new RedisManager( - app, - { - connection: 'primary', - connections: { - primary: { - host: process.env.REDIS_HOST, - port: Number(process.env.REDIS_PORT), - }, - cluster: { - clusters: clusterNodes, - }, - }, - }, - new Emitter(app) - ) as unknown) as RedisManagerContract - - await redis.set('greeting', 'hello-world') - const greeting = await redis.get('greeting') - - assert.equal(greeting, 'hello-world') - - await redis.del('greeting') - await redis.quit('primary') - }) - - test('run redis commands using the connection method', async (assert) => { - const app = new Application(__dirname, 'web', {}) - const redis = (new RedisManager( - app, - { - connection: 'primary', - connections: { - primary: { - host: process.env.REDIS_HOST, - port: Number(process.env.REDIS_PORT), - }, - cluster: { - clusters: clusterNodes, - }, - }, - }, - new Emitter(app) - ) as unknown) as RedisManagerContract - - await redis.connection().set('greeting', 'hello-world') - const greeting = await redis.connection().get('greeting') - assert.equal(greeting, 'hello-world') - - await redis.connection().del('greeting') - await redis.quit('primary') - }) - - test('re-use connection when connection method is called', async (assert) => { - const app = new Application(__dirname, 'web', {}) - const redis = (new RedisManager( - app, - { - connection: 'primary', - connections: { - primary: { - host: process.env.REDIS_HOST, - port: Number(process.env.REDIS_PORT), - }, - cluster: { - clusters: clusterNodes, - }, - }, - }, - new Emitter(app) - ) as unknown) as RedisManagerContract - - assert.deepEqual(redis.connection(), redis.connection('primary')) - await redis.quit() - }) - - test('connect to redis cluster when cluster array is defined', async (assert, done) => { - const app = new Application(__dirname, 'web', {}) - const redis = (new RedisManager( - app, - { - connection: 'cluster', - connections: { - primary: { - host: process.env.REDIS_HOST, - port: Number(process.env.REDIS_PORT), - }, - cluster: { - clusters: clusterNodes, - }, - }, - }, - new Emitter(app) - ) as unknown) as RedisManagerContract - - redis.connection('cluster').on('ready', async () => { - assert.isAbove(redis.connection('cluster').nodes().length, 2) - await redis.quit() - done() - }) - }) - - test('on disconnect clear connection from tracked list', async (assert, done) => { - const app = new Application(__dirname, 'web', {}) - const redis = (new RedisManager( - app, - { - connection: 'primary', - connections: { - primary: { - host: process.env.REDIS_HOST, - port: Number(process.env.REDIS_PORT), - }, - cluster: { - clusters: clusterNodes, - }, - }, - }, - new Emitter(app) - ) as unknown) as RedisManagerContract - - const connection = redis.connection() - connection.on('end', () => { - assert.equal(redis.activeConnectionsCount, 0) - done() - }) - - connection.on('ready', async () => { - await redis.quit() - }) - }) - - test('get report for connections marked for healthChecks', async (assert) => { - const app = new Application(__dirname, 'web', {}) - const redis = new RedisManager( - app, - { - connection: 'primary', - connections: { - primary: { - host: process.env.REDIS_HOST, - port: Number(process.env.REDIS_PORT), - healthCheck: true, - }, - secondary: { - host: process.env.REDIS_HOST, - port: 4444, - }, - }, - } as any, - new Emitter(app) - ) - - const report = await redis.report() - assert.deepEqual(report.health, { healthy: true, message: 'All connections are healthy' }) - assert.lengthOf(report.meta, 1) - assert.isDefined(report.meta[0].used_memory) - assert.equal(report.meta[0].status, 'ready') - await redis.quit() - }) - - test('generate correct report when one of the connections are broken', async (assert) => { - const app = new Application(__dirname, 'web', {}) - const redis = new RedisManager( - app, - { - connection: 'primary', - connections: { - primary: { - host: process.env.REDIS_HOST, - port: Number(process.env.REDIS_PORT), - healthCheck: true, - }, - secondary: { - host: process.env.REDIS_HOST, - healthCheck: true, - port: 4444, - }, - }, - } as any, - new Emitter(app) - ) - - const report = await redis.report() - - assert.deepEqual(report.health, { - healthy: false, - message: 'One or more redis connections are not healthy', - }) - assert.lengthOf(report.meta, 2) - await redis.quit() - }) - - test('use pub/sub using the manager instance', async (assert, done) => { - const app = new Application(__dirname, 'web', {}) - const redis = (new RedisManager( - app, - { - connection: 'primary', - connections: { - primary: { - host: process.env.REDIS_HOST, - port: Number(process.env.REDIS_PORT), - }, - cluster: { - clusters: clusterNodes, - }, - }, - }, - new Emitter(app) - ) as unknown) as RedisManagerContract - - redis.connection().on('subscription:ready', () => { - redis.publish('news', 'breaking news at 9') - }) - - redis.subscribe('news', async (message) => { - assert.equal(message, 'breaking news at 9') - await redis.quit() - done() - }) - }) + test('run redis commands using default connection', async (assert) => { + const app = new Application(__dirname, 'web', {}) + const redis = (new RedisManager( + app, + { + connection: 'primary', + connections: { + primary: { + host: process.env.REDIS_HOST, + port: Number(process.env.REDIS_PORT), + }, + cluster: { + clusters: clusterNodes, + }, + }, + }, + new Emitter(app) + ) as unknown) as RedisManagerContract + + await redis.set('greeting', 'hello-world') + const greeting = await redis.get('greeting') + + assert.equal(greeting, 'hello-world') + + await redis.del('greeting') + await redis.quit('primary') + }) + + test('run redis commands using the connection method', async (assert) => { + const app = new Application(__dirname, 'web', {}) + const redis = (new RedisManager( + app, + { + connection: 'primary', + connections: { + primary: { + host: process.env.REDIS_HOST, + port: Number(process.env.REDIS_PORT), + }, + cluster: { + clusters: clusterNodes, + }, + }, + }, + new Emitter(app) + ) as unknown) as RedisManagerContract + + await redis.connection().set('greeting', 'hello-world') + const greeting = await redis.connection().get('greeting') + assert.equal(greeting, 'hello-world') + + await redis.connection().del('greeting') + await redis.quit('primary') + }) + + test('re-use connection when connection method is called', async (assert) => { + const app = new Application(__dirname, 'web', {}) + const redis = (new RedisManager( + app, + { + connection: 'primary', + connections: { + primary: { + host: process.env.REDIS_HOST, + port: Number(process.env.REDIS_PORT), + }, + cluster: { + clusters: clusterNodes, + }, + }, + }, + new Emitter(app) + ) as unknown) as RedisManagerContract + + assert.deepEqual(redis.connection(), redis.connection('primary')) + await redis.quit() + }) + + test('connect to redis cluster when cluster array is defined', async (assert, done) => { + const app = new Application(__dirname, 'web', {}) + const redis = (new RedisManager( + app, + { + connection: 'cluster', + connections: { + primary: { + host: process.env.REDIS_HOST, + port: Number(process.env.REDIS_PORT), + }, + cluster: { + clusters: clusterNodes, + }, + }, + }, + new Emitter(app) + ) as unknown) as RedisManagerContract + + redis.connection('cluster').on('ready', async () => { + assert.isAbove(redis.connection('cluster').nodes().length, 2) + await redis.quit() + done() + }) + }) + + test('on disconnect clear connection from tracked list', async (assert, done) => { + const app = new Application(__dirname, 'web', {}) + const redis = (new RedisManager( + app, + { + connection: 'primary', + connections: { + primary: { + host: process.env.REDIS_HOST, + port: Number(process.env.REDIS_PORT), + }, + cluster: { + clusters: clusterNodes, + }, + }, + }, + new Emitter(app) + ) as unknown) as RedisManagerContract + + const connection = redis.connection() + connection.on('end', () => { + assert.equal(redis.activeConnectionsCount, 0) + done() + }) + + connection.on('ready', async () => { + await redis.quit() + }) + }) + + test('get report for connections marked for healthChecks', async (assert) => { + const app = new Application(__dirname, 'web', {}) + const redis = new RedisManager( + app, + { + connection: 'primary', + connections: { + primary: { + host: process.env.REDIS_HOST, + port: Number(process.env.REDIS_PORT), + healthCheck: true, + }, + secondary: { + host: process.env.REDIS_HOST, + port: 4444, + }, + }, + } as any, + new Emitter(app) + ) + + const report = await redis.report() + assert.deepEqual(report.health, { healthy: true, message: 'All connections are healthy' }) + assert.lengthOf(report.meta, 1) + assert.isDefined(report.meta[0].used_memory) + assert.equal(report.meta[0].status, 'ready') + await redis.quit() + }) + + test('generate correct report when one of the connections are broken', async (assert) => { + const app = new Application(__dirname, 'web', {}) + const redis = new RedisManager( + app, + { + connection: 'primary', + connections: { + primary: { + host: process.env.REDIS_HOST, + port: Number(process.env.REDIS_PORT), + healthCheck: true, + }, + secondary: { + host: process.env.REDIS_HOST, + healthCheck: true, + port: 4444, + }, + }, + } as any, + new Emitter(app) + ) + + const report = await redis.report() + + assert.deepEqual(report.health, { + healthy: false, + message: 'One or more redis connections are not healthy', + }) + assert.lengthOf(report.meta, 2) + await redis.quit() + }) + + test('use pub/sub using the manager instance', async (assert, done) => { + const app = new Application(__dirname, 'web', {}) + const redis = (new RedisManager( + app, + { + connection: 'primary', + connections: { + primary: { + host: process.env.REDIS_HOST, + port: Number(process.env.REDIS_PORT), + }, + cluster: { + clusters: clusterNodes, + }, + }, + }, + new Emitter(app) + ) as unknown) as RedisManagerContract + + redis.connection().on('subscription:ready', () => { + redis.publish('news', 'breaking news at 9') + }) + + redis.subscribe('news', async (message) => { + assert.equal(message, 'breaking news at 9') + await redis.quit() + done() + }) + }) }) diff --git a/tsconfig.json b/tsconfig.json index aa93a1a..c366038 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,8 +1,8 @@ { - "extends": "./node_modules/@adonisjs/mrm-preset/_tsconfig", - "compilerOptions": { - "esModuleInterop": true, - "skipLibCheck": true, - "types": ["@adonisjs/core", "@adonisjs/repl", "@types/node"] - } + "extends": "./node_modules/@adonisjs/mrm-preset/_tsconfig", + "compilerOptions": { + "esModuleInterop": true, + "skipLibCheck": true, + "types": ["@adonisjs/core", "@adonisjs/repl", "@types/node"] + } }