1- import { Connection , Options } from 'amqplib' ;
2- import { ERROR_EVENT , RQM_DEFAULT_URL , RQM_DEFAULT_QUEUE , RQM_DEFAULT_PREFETCH_COUNT , RQM_DEFAULT_IS_GLOBAL_PREFETCH_COUNT , RQM_DEFAULT_QUEUE_OPTIONS , CONNECT_EVENT , DISCONNECT_EVENT } from './../constants' ;
31import { Logger } from '@nestjs/common/services/logger.service' ;
42import { loadPackage } from '@nestjs/common/utils/load-package.util' ;
5- import { ClientProxy } from './client-proxy' ;
3+ import { randomStringGenerator } from '@nestjs/common/utils/random-string-generator.util' ;
4+ import * as amqp from 'amqp-connection-manager' ;
5+ import { EventEmitter } from 'events' ;
6+ import { fromEvent , merge , Observable } from 'rxjs' ;
7+ import { first , map , share , switchMap } from 'rxjs/operators' ;
68import { ClientOptions , RmqOptions } from '../interfaces' ;
9+ import {
10+ DISCONNECT_EVENT ,
11+ ERROR_EVENT ,
12+ RQM_DEFAULT_IS_GLOBAL_PREFETCH_COUNT ,
13+ RQM_DEFAULT_PREFETCH_COUNT ,
14+ RQM_DEFAULT_QUEUE ,
15+ RQM_DEFAULT_QUEUE_OPTIONS ,
16+ RQM_DEFAULT_URL ,
17+ } from './../constants' ;
718import { WritePacket } from './../interfaces' ;
8- import { EventEmitter } from 'events' ;
9- import * as amqp from 'amqp-connection-manager' ;
19+ import { ClientProxy } from './client-proxy' ;
1020
1121let rqmPackage : any = { } ;
1222
1323export class ClientRMQ extends ClientProxy {
14- private readonly logger = new Logger ( ClientProxy . name ) ;
15- private client : any = null ;
16- private channel : any = null ;
17- private urls : string [ ] ;
18- private queue : string ;
19- private prefetchCount : number ;
20- private isGlobalPrefetchCount : boolean ;
21- private queueOptions : Options . AssertQueue ;
22- private replyQueue : string ;
23- private responseEmitter : EventEmitter ;
24-
25- constructor (
26- private readonly options : ClientOptions [ 'options' ] ) {
27- super ( ) ;
28- this . urls =
29- this . getOptionsProp < RmqOptions > ( this . options , 'urls' ) || [ RQM_DEFAULT_URL ] ;
30- this . queue =
31- this . getOptionsProp < RmqOptions > ( this . options , 'queue' ) || RQM_DEFAULT_QUEUE ;
32- this . prefetchCount =
33- this . getOptionsProp < RmqOptions > ( this . options , 'prefetchCount' ) || RQM_DEFAULT_PREFETCH_COUNT ;
34- this . isGlobalPrefetchCount =
35- this . getOptionsProp < RmqOptions > ( this . options , 'isGlobalPrefetchCount' ) || RQM_DEFAULT_IS_GLOBAL_PREFETCH_COUNT ;
36- this . queueOptions =
37- this . getOptionsProp < RmqOptions > ( this . options , 'queueOptions' ) || RQM_DEFAULT_QUEUE_OPTIONS ;
38- rqmPackage = loadPackage ( 'amqplib' , ClientRMQ . name ) ;
39- this . connect ( ) ;
40- }
24+ protected readonly logger = new Logger ( ClientProxy . name ) ;
25+ protected connection : Promise < any > ;
26+ protected client : any = null ;
27+ protected channel : any = null ;
28+ protected urls : string [ ] ;
29+ protected queue : string ;
30+ protected prefetchCount : number ;
31+ protected isGlobalPrefetchCount : boolean ;
32+ protected queueOptions : any ;
33+ protected replyQueue : string ;
34+ protected responseEmitter : EventEmitter ;
4135
42- public close ( ) : void {
43- this . channel && this . channel . close ( ) ;
44- this . client && this . client . close ( ) ;
45- }
36+ constructor ( protected readonly options : ClientOptions [ 'options' ] ) {
37+ super ( ) ;
38+ this . urls = this . getOptionsProp < RmqOptions > ( this . options , 'urls' ) || [
39+ RQM_DEFAULT_URL ,
40+ ] ;
41+ this . queue =
42+ this . getOptionsProp < RmqOptions > ( this . options , 'queue' ) ||
43+ RQM_DEFAULT_QUEUE ;
44+ this . prefetchCount =
45+ this . getOptionsProp < RmqOptions > ( this . options , 'prefetchCount' ) ||
46+ RQM_DEFAULT_PREFETCH_COUNT ;
47+ this . isGlobalPrefetchCount =
48+ this . getOptionsProp < RmqOptions > ( this . options , 'isGlobalPrefetchCount' ) ||
49+ RQM_DEFAULT_IS_GLOBAL_PREFETCH_COUNT ;
50+ this . queueOptions =
51+ this . getOptionsProp < RmqOptions > ( this . options , 'queueOptions' ) ||
52+ RQM_DEFAULT_QUEUE_OPTIONS ;
4653
47- public listen ( ) {
48- this . channel . addSetup ( ( channel ) => {
49- return Promise . all ( [
50- channel . consume ( this . replyQueue , ( msg ) => {
51- this . responseEmitter . emit ( msg . properties . correlationId , msg ) ;
52- } , { noAck : true } ) ,
53- ] ) ;
54- } ) ;
55- }
54+ rqmPackage = loadPackage ( 'amqplib' , ClientRMQ . name ) ;
55+ }
5656
57- public connect ( ) : Promise < any > {
58- if ( this . client && this . channel ) {
59- return Promise . resolve ( ) ;
60- }
61- return new Promise ( async ( resolve , reject ) => {
62- this . client = amqp . connect ( this . urls ) ;
63- this . client . on ( CONNECT_EVENT , x => {
64- this . channel = this . client . createChannel ( {
65- json : false ,
66- setup : async ( channel ) => {
67- await channel . assertQueue ( this . queue , this . queueOptions ) ;
68- await channel . prefetch ( this . prefetchCount , this . isGlobalPrefetchCount ) ;
69- this . replyQueue = ( await channel . assertQueue ( '' , { exclusive : true } ) ) . queue ;
70- this . responseEmitter = new EventEmitter ( ) ;
71- this . responseEmitter . setMaxListeners ( 0 ) ;
72- this . listen ( ) ;
73- resolve ( ) ;
74- } ,
75- } ) ;
76- } ) ;
77- this . client . on ( DISCONNECT_EVENT , err => {
78- reject ( err ) ;
79- this . client . close ( ) ;
80- this . client = null ;
81- } ) ;
82- } ) ;
83- }
57+ public close ( ) : void {
58+ this . channel && this . channel . close ( ) ;
59+ this . client && this . client . close ( ) ;
60+ }
61+
62+ public listen ( ) {
63+ this . channel . addSetup ( channel =>
64+ channel . consume (
65+ this . replyQueue ,
66+ msg => {
67+ this . responseEmitter . emit ( msg . properties . correlationId , msg ) ;
68+ } ,
69+ { noAck : true } ,
70+ ) ,
71+ ) ;
72+ }
8473
85- protected publish ( messageObj , callback : ( packet : WritePacket ) => any ) {
86- if ( ! this . client ) {
87- this . connect ( ) . then ( x => {
88- this . sendMessage ( messageObj , callback ) ;
89- } ) ;
90- } else {
91- this . sendMessage ( messageObj , callback ) ;
92- }
74+ public connect ( ) : Promise < any > {
75+ if ( this . client && this . channel ) {
76+ return this . connection ;
9377 }
78+ this . client = this . createClient ( ) ;
79+ this . handleError ( this . client ) ;
80+
81+ const connect$ = this . connect$ ( this . client ) ;
82+ this . connection = this . mergeDisconnectEvent ( this . client , connect$ )
83+ . pipe (
84+ switchMap ( ( ) => {
85+ return new Promise ( resolve =>
86+ this . client . createChannel ( {
87+ json : false ,
88+ setup : async channel => this . setupChannel ( channel , resolve ) ,
89+ } ) ,
90+ ) ;
91+ } ) ,
92+ share ( ) ,
93+ )
94+ . toPromise ( ) ;
95+ return this . connection ;
96+ }
97+
98+ public createClient < T = any > ( ) : T {
99+ return amqp . connect ( this . urls ) as T ;
100+ }
101+
102+ public mergeDisconnectEvent < T = any > (
103+ instance : any ,
104+ source$ : Observable < T > ,
105+ ) : Observable < T > {
106+ const close$ = fromEvent ( instance , DISCONNECT_EVENT ) . pipe (
107+ map ( err => {
108+ throw err ;
109+ } ) ,
110+ ) ;
111+ return merge ( source$ , close$ ) . pipe ( first ( ) ) ;
112+ }
94113
95- private sendMessage ( messageObj , callback : ( packet : WritePacket ) => any ) {
96- try {
97- const correlationId = Math . random ( ) . toString ( 36 ) . substring ( 2 , 15 ) + Math . random ( ) . toString ( 36 ) . substring ( 2 , 15 ) ;
98- this . responseEmitter . on ( correlationId , msg => {
99- const { content } = msg ;
100- this . handleMessage ( content , callback ) ;
101- } ) ;
102- this . channel . sendToQueue ( this . queue , Buffer . from ( JSON . stringify ( messageObj ) ) , {
103- replyTo : this . replyQueue ,
104- correlationId,
105- } ) ;
106- } catch ( err ) {
107- this . logger . error ( err ) ;
108- callback ( { err } ) ;
109- }
114+ public async setupChannel ( channel : any , resolve : Function ) {
115+ await channel . assertQueue ( this . queue , this . queueOptions ) ;
116+ await channel . prefetch ( this . prefetchCount , this . isGlobalPrefetchCount ) ;
117+ this . replyQueue = ( await channel . assertQueue ( '' , {
118+ exclusive : true ,
119+ } ) ) . queue ;
120+
121+ this . responseEmitter = new EventEmitter ( ) ;
122+ this . responseEmitter . setMaxListeners ( 0 ) ;
123+ this . listen ( ) ;
124+
125+ resolve ( ) ;
126+ }
127+
128+ protected publish (
129+ message : any ,
130+ callback : ( packet : WritePacket ) => any ,
131+ ) : Function {
132+ try {
133+ const correlationId = randomStringGenerator ( ) ;
134+ const listener = msg => {
135+ const { content } = msg ;
136+ this . handleMessage ( content , callback ) ;
137+ } ;
138+ this . responseEmitter . on ( correlationId , listener ) ;
139+ this . channel . sendToQueue (
140+ this . queue ,
141+ Buffer . from ( JSON . stringify ( message ) ) ,
142+ {
143+ replyTo : this . replyQueue ,
144+ correlationId,
145+ } ,
146+ ) ;
147+ return ( ) => this . responseEmitter . removeListener ( correlationId , listener ) ;
148+ } catch ( err ) {
149+ callback ( { err } ) ;
110150 }
151+ }
111152
112- public handleMessage (
113- msg : WritePacket ,
114- callback : ( packet : WritePacket ) => any ,
115- ) {
116- const { err, response, isDisposed } = JSON . parse ( msg . toString ( ) ) ;
117- if ( isDisposed || err ) {
118- callback ( {
119- err,
120- response : null ,
121- isDisposed : true ,
122- } ) ;
123- }
124- callback ( {
125- err,
126- response,
127- } ) ;
128- }
129-
130- public handleError ( client : Connection ) : void {
131- client . addListener ( ERROR_EVENT , err => this . logger . error ( err ) ) ;
153+ public handleMessage (
154+ packet : WritePacket ,
155+ callback : ( packet : WritePacket ) => any ,
156+ ) {
157+ const { err, response, isDisposed } = packet ;
158+ if ( isDisposed || err ) {
159+ callback ( {
160+ err,
161+ response : null ,
162+ isDisposed : true ,
163+ } ) ;
132164 }
133- }
165+ callback ( {
166+ err,
167+ response,
168+ } ) ;
169+ }
170+
171+ public handleError ( client : any ) : void {
172+ client . addListener ( ERROR_EVENT , err => this . logger . error ( err ) ) ;
173+ }
174+ }
0 commit comments