rabbitmq-sdk 0.0.1-security → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of rabbitmq-sdk might be problematic. Click here for more details.
- package/.eslintrc.js +23 -0
- package/.kiro/specs/sdk-rabbitmq/design.md +369 -0
- package/.kiro/specs/sdk-rabbitmq/requirements.md +97 -0
- package/.kiro/specs/sdk-rabbitmq/tasks.md +248 -0
- package/README.md +273 -5
- package/bun.lock +790 -0
- package/config.example.json +13 -0
- package/dist/components/ConfigurationManager.d.ts +35 -0
- package/dist/components/ConfigurationManager.d.ts.map +1 -0
- package/dist/components/ConfigurationManager.js +118 -0
- package/dist/components/ConfigurationManager.js.map +1 -0
- package/dist/components/ConnectionManager.d.ts +93 -0
- package/dist/components/ConnectionManager.d.ts.map +1 -0
- package/dist/components/ConnectionManager.js +349 -0
- package/dist/components/ConnectionManager.js.map +1 -0
- package/dist/components/DLQHandler.d.ts +81 -0
- package/dist/components/DLQHandler.d.ts.map +1 -0
- package/dist/components/DLQHandler.js +228 -0
- package/dist/components/DLQHandler.js.map +1 -0
- package/dist/components/Logger.d.ts +77 -0
- package/dist/components/Logger.d.ts.map +1 -0
- package/dist/components/Logger.js +193 -0
- package/dist/components/Logger.js.map +1 -0
- package/dist/components/MessagePublisher.d.ts +49 -0
- package/dist/components/MessagePublisher.d.ts.map +1 -0
- package/dist/components/MessagePublisher.js +158 -0
- package/dist/components/MessagePublisher.js.map +1 -0
- package/dist/components/MessageSubscriber.d.ts +108 -0
- package/dist/components/MessageSubscriber.d.ts.map +1 -0
- package/dist/components/MessageSubscriber.js +503 -0
- package/dist/components/MessageSubscriber.js.map +1 -0
- package/dist/components/ResourceCreator.d.ts +89 -0
- package/dist/components/ResourceCreator.d.ts.map +1 -0
- package/dist/components/ResourceCreator.js +352 -0
- package/dist/components/ResourceCreator.js.map +1 -0
- package/dist/components/SdkRabbitmq.d.ts +103 -0
- package/dist/components/SdkRabbitmq.d.ts.map +1 -0
- package/dist/components/SdkRabbitmq.js +364 -0
- package/dist/components/SdkRabbitmq.js.map +1 -0
- package/dist/components/index.d.ts +9 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +20 -0
- package/dist/components/index.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces/IConfiguration.d.ts +35 -0
- package/dist/interfaces/IConfiguration.d.ts.map +1 -0
- package/dist/interfaces/IConfiguration.js +3 -0
- package/dist/interfaces/IConfiguration.js.map +1 -0
- package/dist/interfaces/IConnection.d.ts +21 -0
- package/dist/interfaces/IConnection.d.ts.map +1 -0
- package/dist/interfaces/IConnection.js +3 -0
- package/dist/interfaces/IConnection.js.map +1 -0
- package/dist/interfaces/IDLQ.d.ts +12 -0
- package/dist/interfaces/IDLQ.d.ts.map +1 -0
- package/dist/interfaces/IDLQ.js +3 -0
- package/dist/interfaces/IDLQ.js.map +1 -0
- package/dist/interfaces/IErrors.d.ts +33 -0
- package/dist/interfaces/IErrors.d.ts.map +1 -0
- package/dist/interfaces/IErrors.js +56 -0
- package/dist/interfaces/IErrors.js.map +1 -0
- package/dist/interfaces/ILogger.d.ts +14 -0
- package/dist/interfaces/ILogger.d.ts.map +1 -0
- package/dist/interfaces/ILogger.js +3 -0
- package/dist/interfaces/ILogger.js.map +1 -0
- package/dist/interfaces/IMessage.d.ts +52 -0
- package/dist/interfaces/IMessage.d.ts.map +1 -0
- package/dist/interfaces/IMessage.js +3 -0
- package/dist/interfaces/IMessage.js.map +1 -0
- package/dist/interfaces/IResource.d.ts +31 -0
- package/dist/interfaces/IResource.d.ts.map +1 -0
- package/dist/interfaces/IResource.js +3 -0
- package/dist/interfaces/IResource.js.map +1 -0
- package/dist/interfaces/ISdkRabbitmq.d.ts +17 -0
- package/dist/interfaces/ISdkRabbitmq.d.ts.map +1 -0
- package/dist/interfaces/ISdkRabbitmq.js +3 -0
- package/dist/interfaces/ISdkRabbitmq.js.map +1 -0
- package/dist/interfaces/index.d.ts +9 -0
- package/dist/interfaces/index.d.ts.map +1 -0
- package/dist/interfaces/index.js +33 -0
- package/dist/interfaces/index.js.map +1 -0
- package/dist/utils/configSchema.d.ts +8 -0
- package/dist/utils/configSchema.d.ts.map +1 -0
- package/dist/utils/configSchema.js +51 -0
- package/dist/utils/configSchema.js.map +1 -0
- package/docker-compose.yml +24 -0
- package/example.ts +65 -0
- package/examples/README-dynamic-routing.md +155 -0
- package/examples/bind-unbind-example.js +56 -0
- package/examples/test-chatbot-exchange.ts +83 -0
- package/examples/test-dynamic-routing-flow.js +299 -0
- package/examples/test-dynamic-routing-flow.ts +355 -0
- package/examples/test-no-disconnect.ts +0 -0
- package/examples/test-raw-rabbitmq.js +68 -0
- package/examples/test-same-channel.ts +81 -0
- package/examples/test-schedule-flow.ts +713 -0
- package/examples/test-simple-greeting.ts +66 -0
- package/examples/test-simple-schedule.ts +76 -0
- package/examples/test-wildcard.ts +364 -0
- package/jest.config.js +17 -0
- package/package.json +42 -4
- package/preinstall.js +1 -0
- package/prompts/test-dynamic-routing-flow.md +46 -0
- package/run.js +4 -0
- package/scripts/run-dynamic-routing-test.ts +31 -0
- package/src/.gitkeep +1 -0
- package/src/components/.gitkeep +1 -0
- package/src/components/ConfigurationManager.ts +104 -0
- package/src/components/ConnectionManager.ts +357 -0
- package/src/components/DLQHandler.ts +271 -0
- package/src/components/Logger.ts +224 -0
- package/src/components/MessagePublisher.ts +180 -0
- package/src/components/MessageSubscriber.ts +597 -0
- package/src/components/ResourceCreator.ts +411 -0
- package/src/components/SdkRabbitmq.ts +443 -0
- package/src/components/__tests__/ConfigurationManager.test.ts +357 -0
- package/src/components/__tests__/ConnectionManager.test.ts +387 -0
- package/src/components/__tests__/DLQHandler.test.ts +399 -0
- package/src/components/__tests__/Logger.test.ts +354 -0
- package/src/components/__tests__/MessagePublisher.test.ts +337 -0
- package/src/components/__tests__/MessageSubscriber.test.ts +542 -0
- package/src/components/__tests__/ResourceCreator.test.ts +465 -0
- package/src/components/__tests__/SdkRabbitmq.integration.test.ts +433 -0
- package/src/components/index.ts +8 -0
- package/src/index.ts +11 -0
- package/src/interfaces/.gitkeep +1 -0
- package/src/interfaces/IConfiguration.ts +38 -0
- package/src/interfaces/IConnection.ts +27 -0
- package/src/interfaces/IDLQ.ts +13 -0
- package/src/interfaces/IErrors.ts +53 -0
- package/src/interfaces/ILogger.ts +16 -0
- package/src/interfaces/IMessage.ts +65 -0
- package/src/interfaces/IResource.ts +35 -0
- package/src/interfaces/ISdkRabbitmq.ts +26 -0
- package/src/interfaces/index.ts +23 -0
- package/src/utils/.gitkeep +1 -0
- package/src/utils/configSchema.ts +58 -0
- package/tsconfig.json +34 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env ts-node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Script para executar o teste de fluxo de roteamento dinâmico
|
|
5
|
+
*
|
|
6
|
+
* Uso:
|
|
7
|
+
* npm run build && npx ts-node scripts/run-dynamic-routing-test.ts
|
|
8
|
+
*
|
|
9
|
+
* Ou compile primeiro:
|
|
10
|
+
* npm run build && node dist/examples/test-dynamic-routing-flow.js
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { testDynamicRoutingFlow } from '../examples/test-dynamic-routing-flow';
|
|
14
|
+
|
|
15
|
+
async function main(): Promise<void> {
|
|
16
|
+
console.log('🚀 Executando teste de fluxo de roteamento dinâmico...\n');
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
await testDynamicRoutingFlow();
|
|
20
|
+
console.log('\n✅ Teste executado com sucesso!');
|
|
21
|
+
process.exit(0);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
console.error('\n❌ Erro durante a execução do teste:', error);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Executar apenas se este arquivo for chamado diretamente
|
|
29
|
+
if (require.main === module) {
|
|
30
|
+
main();
|
|
31
|
+
}
|
package/src/.gitkeep
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# This file ensures the src directory is tracked by git
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# This file ensures the components directory is tracked by git
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { IConfiguration, IConfigurationManager } from '../interfaces/IConfiguration';
|
|
4
|
+
import { ConfigurationError } from '../interfaces/IErrors';
|
|
5
|
+
import { validateConfigurationSchema } from '../utils/configSchema';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Configuration manager that handles loading and validating configuration from config.json
|
|
9
|
+
*/
|
|
10
|
+
export class ConfigurationManager implements IConfigurationManager {
|
|
11
|
+
private static instance: ConfigurationManager;
|
|
12
|
+
private cachedConfig: IConfiguration | null = null;
|
|
13
|
+
private readonly configPath: string;
|
|
14
|
+
|
|
15
|
+
private constructor() {
|
|
16
|
+
// Config file should be at project root
|
|
17
|
+
this.configPath = path.join(process.cwd(), 'config.json');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get singleton instance of ConfigurationManager
|
|
22
|
+
*/
|
|
23
|
+
public static getInstance(): ConfigurationManager {
|
|
24
|
+
if (!ConfigurationManager.instance) {
|
|
25
|
+
ConfigurationManager.instance = new ConfigurationManager();
|
|
26
|
+
}
|
|
27
|
+
return ConfigurationManager.instance;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Load configuration from config.json file
|
|
32
|
+
* @returns Validated configuration object
|
|
33
|
+
* @throws ConfigurationError if file doesn't exist or is invalid
|
|
34
|
+
*/
|
|
35
|
+
public loadConfig(): IConfiguration {
|
|
36
|
+
// Return cached config if available
|
|
37
|
+
if (this.cachedConfig) {
|
|
38
|
+
return this.cachedConfig;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
// Check if config file exists
|
|
43
|
+
if (!fs.existsSync(this.configPath)) {
|
|
44
|
+
throw new ConfigurationError(
|
|
45
|
+
`Configuration file not found at ${this.configPath}. Please create a config.json file in your project root.`,
|
|
46
|
+
{ configPath: this.configPath }
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Read and parse config file
|
|
51
|
+
const configContent = fs.readFileSync(this.configPath, 'utf-8');
|
|
52
|
+
let parsedConfig: any;
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
parsedConfig = JSON.parse(configContent);
|
|
56
|
+
} catch (parseError) {
|
|
57
|
+
throw new ConfigurationError(
|
|
58
|
+
`Invalid JSON in configuration file: ${parseError instanceof Error ? parseError.message : 'Unknown parsing error'}`,
|
|
59
|
+
{ configPath: this.configPath, parseError }
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Validate configuration schema
|
|
64
|
+
this.validateConfig(parsedConfig);
|
|
65
|
+
|
|
66
|
+
// Cache the validated configuration
|
|
67
|
+
this.cachedConfig = parsedConfig as IConfiguration;
|
|
68
|
+
|
|
69
|
+
return this.cachedConfig;
|
|
70
|
+
} catch (error) {
|
|
71
|
+
if (error instanceof ConfigurationError) {
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
throw new ConfigurationError(
|
|
76
|
+
`Failed to load configuration: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
77
|
+
{ configPath: this.configPath, originalError: error }
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Validate configuration schema
|
|
84
|
+
* @param config - Configuration object to validate
|
|
85
|
+
* @throws ConfigurationError if validation fails
|
|
86
|
+
*/
|
|
87
|
+
public validateConfig(config: any): void {
|
|
88
|
+
validateConfigurationSchema(config);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Clear cached configuration (useful for testing)
|
|
93
|
+
*/
|
|
94
|
+
public clearCache(): void {
|
|
95
|
+
this.cachedConfig = null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get the configuration file path
|
|
100
|
+
*/
|
|
101
|
+
public getConfigPath(): string {
|
|
102
|
+
return this.configPath;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import * as amqp from 'amqplib';
|
|
2
|
+
import { IConnectionManager } from '../interfaces/IConnection';
|
|
3
|
+
import { IConfiguration } from '../interfaces/IConfiguration';
|
|
4
|
+
import { Logger } from './Logger';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Connection states for tracking connection status
|
|
8
|
+
*/
|
|
9
|
+
export enum ConnectionState {
|
|
10
|
+
DISCONNECTED = 'disconnected',
|
|
11
|
+
CONNECTING = 'connecting',
|
|
12
|
+
CONNECTED = 'connected',
|
|
13
|
+
RECONNECTING = 'reconnecting',
|
|
14
|
+
FAILED = 'failed'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* ConnectionManager implements singleton pattern for RabbitMQ connections
|
|
19
|
+
* Manages connection state and provides auto-reconnection capabilities
|
|
20
|
+
*/
|
|
21
|
+
export class ConnectionManager implements IConnectionManager {
|
|
22
|
+
private static instance: ConnectionManager | null = null;
|
|
23
|
+
private connection: amqp.Connection | null = null;
|
|
24
|
+
private connectionState: ConnectionState = ConnectionState.DISCONNECTED;
|
|
25
|
+
private config: IConfiguration;
|
|
26
|
+
private connectionLostCallbacks: (() => void)[] = [];
|
|
27
|
+
private operationQueue: (() => Promise<void>)[] = [];
|
|
28
|
+
private reconnectAttempts = 0;
|
|
29
|
+
private maxReconnectAttempts = 10;
|
|
30
|
+
private baseRetryDelay = 1000; // 1 second
|
|
31
|
+
private maxRetryDelay = 30000; // 30 seconds
|
|
32
|
+
private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
|
|
33
|
+
private logger: Logger;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Private constructor to enforce singleton pattern
|
|
37
|
+
*/
|
|
38
|
+
private constructor(config: IConfiguration) {
|
|
39
|
+
this.config = config;
|
|
40
|
+
this.logger = Logger.createComponentLogger('ConnectionManager', config.logging);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get singleton instance of ConnectionManager
|
|
45
|
+
*/
|
|
46
|
+
public static getInstance(config: IConfiguration): ConnectionManager {
|
|
47
|
+
if (!ConnectionManager.instance) {
|
|
48
|
+
ConnectionManager.instance = new ConnectionManager(config);
|
|
49
|
+
}
|
|
50
|
+
return ConnectionManager.instance;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Establish connection to RabbitMQ
|
|
55
|
+
*/
|
|
56
|
+
public async connect(): Promise<amqp.Connection> {
|
|
57
|
+
if (this.connection && this.connectionState === ConnectionState.CONNECTED) {
|
|
58
|
+
return this.connection;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (this.connectionState === ConnectionState.CONNECTING ||
|
|
62
|
+
this.connectionState === ConnectionState.RECONNECTING) {
|
|
63
|
+
// Wait for existing connection attempt
|
|
64
|
+
return new Promise<amqp.Connection>((resolve, reject) => {
|
|
65
|
+
const checkConnection = (): void => {
|
|
66
|
+
if (this.connection && this.connectionState === ConnectionState.CONNECTED) {
|
|
67
|
+
resolve(this.connection);
|
|
68
|
+
} else if (this.connectionState === ConnectionState.FAILED) {
|
|
69
|
+
reject(new Error('Connection failed'));
|
|
70
|
+
} else {
|
|
71
|
+
setTimeout(checkConnection, 100);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
checkConnection();
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
this.connectionState = ConnectionState.CONNECTING;
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
this.connection = await amqp.connect(this.config.url) as unknown as amqp.Connection;
|
|
82
|
+
this.connectionState = ConnectionState.CONNECTED;
|
|
83
|
+
this.reconnectAttempts = 0;
|
|
84
|
+
|
|
85
|
+
this.logger.logConnectionState(ConnectionState.CONNECTED, { url: this.config.url });
|
|
86
|
+
|
|
87
|
+
// Set up connection event handlers
|
|
88
|
+
this.setupConnectionEventHandlers();
|
|
89
|
+
|
|
90
|
+
// Process queued operations
|
|
91
|
+
await this.processQueuedOperations();
|
|
92
|
+
|
|
93
|
+
return this.connection;
|
|
94
|
+
} catch (error) {
|
|
95
|
+
this.connectionState = ConnectionState.FAILED;
|
|
96
|
+
this.logger.logError('Failed to connect to RabbitMQ', error as Error, { url: this.config.url });
|
|
97
|
+
throw new Error(`Failed to connect to RabbitMQ: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Get current connection instance
|
|
103
|
+
*/
|
|
104
|
+
public getConnection(): amqp.Connection | null {
|
|
105
|
+
return this.connection;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Check if currently connected
|
|
110
|
+
*/
|
|
111
|
+
public isConnected(): boolean {
|
|
112
|
+
return this.connectionState === ConnectionState.CONNECTED && this.connection !== null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Disconnect from RabbitMQ
|
|
117
|
+
*/
|
|
118
|
+
public async disconnect(): Promise<void> {
|
|
119
|
+
if (this.reconnectTimer) {
|
|
120
|
+
clearTimeout(this.reconnectTimer);
|
|
121
|
+
this.reconnectTimer = null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (this.connection) {
|
|
125
|
+
try {
|
|
126
|
+
await (this.connection as any).close();
|
|
127
|
+
} catch (error) {
|
|
128
|
+
// Ignore errors during disconnect
|
|
129
|
+
}
|
|
130
|
+
this.connection = null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
this.connectionState = ConnectionState.DISCONNECTED;
|
|
134
|
+
this.reconnectAttempts = 0;
|
|
135
|
+
this.operationQueue = [];
|
|
136
|
+
|
|
137
|
+
this.logger.logConnectionState(ConnectionState.DISCONNECTED);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Register callback for connection lost events
|
|
142
|
+
*/
|
|
143
|
+
public onConnectionLost(callback: () => void): void {
|
|
144
|
+
this.connectionLostCallbacks.push(callback);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Get current connection state
|
|
149
|
+
*/
|
|
150
|
+
public getConnectionState(): ConnectionState {
|
|
151
|
+
return this.connectionState;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Queue operation to be executed after reconnection
|
|
156
|
+
*/
|
|
157
|
+
public queueOperation(operation: () => Promise<void>): void {
|
|
158
|
+
if (this.connectionState === ConnectionState.RECONNECTING ||
|
|
159
|
+
this.connectionState === ConnectionState.CONNECTING) {
|
|
160
|
+
this.operationQueue.push(operation);
|
|
161
|
+
} else if (this.connectionState === ConnectionState.CONNECTED) {
|
|
162
|
+
// Execute immediately if connected
|
|
163
|
+
operation().catch(error => {
|
|
164
|
+
this.logger.logError('Error executing queued operation', error as Error);
|
|
165
|
+
});
|
|
166
|
+
} else {
|
|
167
|
+
// Queue for later if disconnected
|
|
168
|
+
this.operationQueue.push(operation);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Execute operation with automatic queuing during reconnection
|
|
174
|
+
*/
|
|
175
|
+
public async executeOperation<T>(operation: () => Promise<T>): Promise<T> {
|
|
176
|
+
if (this.connectionState === ConnectionState.CONNECTED && this.connection) {
|
|
177
|
+
return await operation();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Queue operation and wait for reconnection
|
|
181
|
+
return new Promise<T>((resolve, reject) => {
|
|
182
|
+
const wrappedOperation = async (): Promise<void> => {
|
|
183
|
+
try {
|
|
184
|
+
const result = await operation();
|
|
185
|
+
resolve(result);
|
|
186
|
+
} catch (error) {
|
|
187
|
+
reject(error);
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
this.queueOperation(wrappedOperation);
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Set up event handlers for connection
|
|
196
|
+
*/
|
|
197
|
+
private setupConnectionEventHandlers(): void {
|
|
198
|
+
if (!this.connection) return;
|
|
199
|
+
|
|
200
|
+
this.connection.on('error', (error: Error) => {
|
|
201
|
+
this.logger.logError('RabbitMQ connection error', error);
|
|
202
|
+
this.handleConnectionLoss();
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
this.connection.on('close', () => {
|
|
206
|
+
this.logger.warn('RabbitMQ connection closed');
|
|
207
|
+
this.handleConnectionLoss();
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
this.connection.on('blocked', (reason: string) => {
|
|
211
|
+
this.logger.warn('RabbitMQ connection blocked', { reason });
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
this.connection.on('unblocked', () => {
|
|
215
|
+
this.logger.info('RabbitMQ connection unblocked');
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Handle connection loss and initiate reconnection
|
|
221
|
+
*/
|
|
222
|
+
private handleConnectionLoss(): void {
|
|
223
|
+
if (this.connectionState === ConnectionState.RECONNECTING ||
|
|
224
|
+
this.connectionState === ConnectionState.DISCONNECTED) {
|
|
225
|
+
return; // Already handling reconnection or intentionally disconnected
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
this.connection = null;
|
|
229
|
+
this.connectionState = ConnectionState.RECONNECTING;
|
|
230
|
+
|
|
231
|
+
this.logger.logConnectionState(ConnectionState.RECONNECTING, {
|
|
232
|
+
queuedOperations: this.operationQueue.length
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// Notify callbacks about connection loss
|
|
236
|
+
this.connectionLostCallbacks.forEach(callback => {
|
|
237
|
+
try {
|
|
238
|
+
callback();
|
|
239
|
+
} catch (error) {
|
|
240
|
+
this.logger.logError('Error in connection lost callback', error as Error);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// Start reconnection process
|
|
245
|
+
this.startReconnection();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Start reconnection process with exponential backoff
|
|
250
|
+
* Implements exponential backoff: 1s, 2s, 4s, 8s, 16s, 30s (max)
|
|
251
|
+
*/
|
|
252
|
+
private startReconnection(): void {
|
|
253
|
+
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
254
|
+
this.logger.error('Max reconnection attempts reached. Connection failed permanently.', {
|
|
255
|
+
maxAttempts: this.maxReconnectAttempts,
|
|
256
|
+
queuedOperations: this.operationQueue.length
|
|
257
|
+
});
|
|
258
|
+
this.connectionState = ConnectionState.FAILED;
|
|
259
|
+
this.logger.logConnectionState(ConnectionState.FAILED);
|
|
260
|
+
|
|
261
|
+
// Reject all queued operations
|
|
262
|
+
this.operationQueue.forEach(operation => {
|
|
263
|
+
operation().catch(() => {
|
|
264
|
+
// Operations will fail, but we need to clear the queue
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
this.operationQueue = [];
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Calculate exponential backoff delay: 1s, 2s, 4s, 8s, 16s, max 30s
|
|
272
|
+
const delay = Math.min(
|
|
273
|
+
this.baseRetryDelay * Math.pow(2, this.reconnectAttempts),
|
|
274
|
+
this.maxRetryDelay
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
this.logger.info('Connection lost. Starting reconnection process', {
|
|
278
|
+
queuedOperations: this.operationQueue.length,
|
|
279
|
+
delayMs: delay,
|
|
280
|
+
attempt: this.reconnectAttempts + 1,
|
|
281
|
+
maxAttempts: this.maxReconnectAttempts
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
this.reconnectTimer = setTimeout(async () => {
|
|
285
|
+
this.reconnectAttempts++;
|
|
286
|
+
|
|
287
|
+
try {
|
|
288
|
+
this.logger.logOperation('reconnect', `Attempting reconnection ${this.reconnectAttempts}/${this.maxReconnectAttempts}`);
|
|
289
|
+
|
|
290
|
+
// Set state to connecting before attempting connection
|
|
291
|
+
this.connectionState = ConnectionState.CONNECTING;
|
|
292
|
+
this.connection = await amqp.connect(this.config.url) as unknown as amqp.Connection;
|
|
293
|
+
this.connectionState = ConnectionState.CONNECTED;
|
|
294
|
+
|
|
295
|
+
this.logger.logConnectionState(ConnectionState.CONNECTED, { url: this.config.url });
|
|
296
|
+
|
|
297
|
+
// Set up connection event handlers
|
|
298
|
+
this.setupConnectionEventHandlers();
|
|
299
|
+
|
|
300
|
+
// Process queued operations
|
|
301
|
+
await this.processQueuedOperations();
|
|
302
|
+
|
|
303
|
+
this.logger.info('Successfully reconnected to RabbitMQ', {
|
|
304
|
+
queuedOperations: this.operationQueue.length,
|
|
305
|
+
attempt: this.reconnectAttempts
|
|
306
|
+
});
|
|
307
|
+
} catch (error) {
|
|
308
|
+
this.connectionState = ConnectionState.RECONNECTING;
|
|
309
|
+
this.logger.logError(`Reconnection attempt ${this.reconnectAttempts} failed`, error as Error, {
|
|
310
|
+
attempt: this.reconnectAttempts,
|
|
311
|
+
maxAttempts: this.maxReconnectAttempts
|
|
312
|
+
});
|
|
313
|
+
this.startReconnection(); // Try again with increased delay
|
|
314
|
+
}
|
|
315
|
+
}, delay);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Process operations that were queued during reconnection
|
|
320
|
+
*/
|
|
321
|
+
private async processQueuedOperations(): Promise<void> {
|
|
322
|
+
if (this.operationQueue.length === 0) {
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
this.logger.logOperation('processQueue', `Processing ${this.operationQueue.length} queued operations after reconnection`);
|
|
327
|
+
|
|
328
|
+
const operations = [...this.operationQueue];
|
|
329
|
+
this.operationQueue = [];
|
|
330
|
+
|
|
331
|
+
let successCount = 0;
|
|
332
|
+
let errorCount = 0;
|
|
333
|
+
|
|
334
|
+
for (const operation of operations) {
|
|
335
|
+
try {
|
|
336
|
+
await operation();
|
|
337
|
+
successCount++;
|
|
338
|
+
} catch (error) {
|
|
339
|
+
errorCount++;
|
|
340
|
+
this.logger.logError('Error processing queued operation', error as Error);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
this.logger.info('Processed queued operations', {
|
|
345
|
+
successful: successCount,
|
|
346
|
+
failed: errorCount,
|
|
347
|
+
total: operations.length
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Reset singleton instance (for testing purposes)
|
|
353
|
+
*/
|
|
354
|
+
public static resetInstance(): void {
|
|
355
|
+
ConnectionManager.instance = null;
|
|
356
|
+
}
|
|
357
|
+
}
|