@theshelf/connection 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +106 -0
- package/dist/ConnectionManager.d.ts +18 -0
- package/dist/ConnectionManager.js +116 -0
- package/dist/definitions/constants.d.ts +7 -0
- package/dist/definitions/constants.js +6 -0
- package/dist/definitions/interfaces.d.ts +5 -0
- package/dist/definitions/interfaces.js +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +2 -0
- package/package.json +27 -0
package/README.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
|
|
2
|
+
# Connection | The Shelf
|
|
3
|
+
|
|
4
|
+
The connection package provides connection resilience for other TheShelf packages by monitoring and restoring connections to external services. It wraps a connectable (such as a database or event broker) and manages its connection lifecycle, ensuring robust error handling and automatic recovery.
|
|
5
|
+
|
|
6
|
+
## Features
|
|
7
|
+
|
|
8
|
+
- **Automatic Connection Monitoring:** Periodically checks and restores lost connections.
|
|
9
|
+
- **Graceful Error Handling:** Connection errors are caught, logged, and do not block application startup.
|
|
10
|
+
- **Configurable Monitoring Interval:** Set custom timeouts for connection checks.
|
|
11
|
+
- **State Management:** Tracks connection states (`DISCONNECTED`, `CONNECTING`, `CONNECTED`, `DISCONNECTING`).
|
|
12
|
+
- **Pluggable Logging:** Integrates with any logger implementing the `@theshelf/logging` interface.
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @theshelf/connection
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Configuration
|
|
21
|
+
|
|
22
|
+
Create a `ConnectionManager` by providing a configuration object:
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
const manager = new ConnectionManager({
|
|
26
|
+
name: 'DATABASE', // Unique name for logging
|
|
27
|
+
connectable, // Must implement Connectable interface (all TheShelf packages are compatible)
|
|
28
|
+
logger, // Must implement Logger interface
|
|
29
|
+
monitoringTimeout: 3000 // Optional, default: 3000ms
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Configuration Properties:**
|
|
34
|
+
|
|
35
|
+
- `name` (string): Unique identifier for the connection (used in logs).
|
|
36
|
+
- `connectable` (Connectable): The object to manage (must implement `connect()` and `disconnect()` methods, and a `connected` property).
|
|
37
|
+
- `logger` (Logger): Logger instance for info, warn, error, and debug messages.
|
|
38
|
+
- `monitoringTimeout` (number, optional): Interval in milliseconds for connection monitoring (default: 3000).
|
|
39
|
+
|
|
40
|
+
## API Reference
|
|
41
|
+
|
|
42
|
+
### `ConnectionManager`
|
|
43
|
+
|
|
44
|
+
#### Properties
|
|
45
|
+
|
|
46
|
+
- `name`: Returns the name of the connection.
|
|
47
|
+
- `state`: Returns the current connection state.
|
|
48
|
+
|
|
49
|
+
#### Methods
|
|
50
|
+
|
|
51
|
+
- `connect(): Promise<void>`
|
|
52
|
+
- Initiates connection. If already connected or connecting, logs a warning.
|
|
53
|
+
- Starts monitoring after successful connection.
|
|
54
|
+
- `disconnect(): Promise<void>`
|
|
55
|
+
- Disconnects the connectable. If already disconnected or disconnecting, logs a warning.
|
|
56
|
+
- Stops monitoring after disconnect.
|
|
57
|
+
|
|
58
|
+
## Monitoring & Recovery
|
|
59
|
+
|
|
60
|
+
After connecting, the manager periodically checks the connection status:
|
|
61
|
+
|
|
62
|
+
- If the connection is lost, it logs a warning and attempts to reconnect automatically.
|
|
63
|
+
- Monitoring can be started/stopped manually via `connect()` and `disconnect()`.
|
|
64
|
+
|
|
65
|
+
## Error Handling
|
|
66
|
+
|
|
67
|
+
- **Connection Errors:**
|
|
68
|
+
- Errors during `connect()` are caught, logged, and do not throw (non-blocking).
|
|
69
|
+
- The manager will keep trying to reconnect at each monitoring interval.
|
|
70
|
+
- **Disconnection Errors:**
|
|
71
|
+
- Errors during `disconnect()` are logged and re-thrown.
|
|
72
|
+
|
|
73
|
+
## Example Usage
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
import ConnectionManager from '@theshelf/connection';
|
|
77
|
+
import { database } from './your-database-instance';
|
|
78
|
+
import { logger } from './your-logger-instance';
|
|
79
|
+
|
|
80
|
+
const manager = new ConnectionManager({
|
|
81
|
+
name: 'DATABASE',
|
|
82
|
+
connectable: database, // a @theshelf/database instance
|
|
83
|
+
monitoringTimeout: 3000, // optional, default: 3000
|
|
84
|
+
logger // must be a @theshelf/logging instance
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Connect and start monitoring
|
|
88
|
+
await manager.connect();
|
|
89
|
+
|
|
90
|
+
// ... your application logic ...
|
|
91
|
+
|
|
92
|
+
// Disconnect and stop monitoring
|
|
93
|
+
await manager.disconnect();
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Connectable Interface
|
|
97
|
+
|
|
98
|
+
Your connectable object should implement the following interface:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
interface Connectable {
|
|
102
|
+
connect(): Promise<void>;
|
|
103
|
+
disconnect(): Promise<void>;
|
|
104
|
+
connected: boolean;
|
|
105
|
+
}
|
|
106
|
+
```
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type Logger from '@theshelf/logging';
|
|
2
|
+
import type { State } from './definitions/constants.js';
|
|
3
|
+
import type { Connectable } from './definitions/interfaces.js';
|
|
4
|
+
type Configuration = {
|
|
5
|
+
readonly name: string;
|
|
6
|
+
readonly connectable: Connectable;
|
|
7
|
+
readonly logger: Logger;
|
|
8
|
+
readonly monitoringTimeout?: number;
|
|
9
|
+
};
|
|
10
|
+
export default class ConnectionManager {
|
|
11
|
+
#private;
|
|
12
|
+
constructor(configuration: Configuration);
|
|
13
|
+
get name(): string;
|
|
14
|
+
get state(): State;
|
|
15
|
+
connect(): Promise<void>;
|
|
16
|
+
disconnect(): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { States } from './definitions/constants.js';
|
|
2
|
+
const DEFAULT_MONITORING_TIMEOUT = 3000;
|
|
3
|
+
export default class ConnectionManager {
|
|
4
|
+
#name;
|
|
5
|
+
#connectable;
|
|
6
|
+
#logger;
|
|
7
|
+
#timeoutDuration;
|
|
8
|
+
#state = States.DISCONNECTED;
|
|
9
|
+
#monitorTimeout;
|
|
10
|
+
#connectPromise;
|
|
11
|
+
#disconnectPromise;
|
|
12
|
+
constructor(configuration) {
|
|
13
|
+
this.#name = configuration.name;
|
|
14
|
+
this.#connectable = configuration.connectable;
|
|
15
|
+
this.#logger = configuration.logger;
|
|
16
|
+
this.#timeoutDuration = configuration.monitoringTimeout ?? DEFAULT_MONITORING_TIMEOUT;
|
|
17
|
+
}
|
|
18
|
+
get name() { return this.#name; }
|
|
19
|
+
get state() { return this.#state; }
|
|
20
|
+
async connect() {
|
|
21
|
+
if (this.#connectPromise !== undefined) {
|
|
22
|
+
this.#logger.logWarn(this.#createLogMessage('connect already in progress'));
|
|
23
|
+
return this.#connectPromise;
|
|
24
|
+
}
|
|
25
|
+
if (this.#state !== States.DISCONNECTED) {
|
|
26
|
+
this.#logger.logWarn(this.#createLogMessage('connect in invalid state'));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
await this.#connect();
|
|
30
|
+
this.#startMonitoring();
|
|
31
|
+
}
|
|
32
|
+
async disconnect() {
|
|
33
|
+
if (this.#disconnectPromise !== undefined) {
|
|
34
|
+
this.#logger.logWarn(this.#createLogMessage('disconnect already in progress'));
|
|
35
|
+
return this.#disconnectPromise;
|
|
36
|
+
}
|
|
37
|
+
if (this.#state !== States.CONNECTED) {
|
|
38
|
+
this.#logger.logWarn(this.#createLogMessage('disconnect in invalid state'));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
this.#stopMonitoring();
|
|
42
|
+
await this.#disconnect();
|
|
43
|
+
}
|
|
44
|
+
async #connect() {
|
|
45
|
+
this.#state = States.CONNECTING;
|
|
46
|
+
try {
|
|
47
|
+
this.#connectPromise = this.#connectable.connect();
|
|
48
|
+
await this.#connectPromise;
|
|
49
|
+
this.#state = States.CONNECTED;
|
|
50
|
+
this.#logger.logInfo(this.#createLogMessage('connected successfully'));
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
this.#state = States.DISCONNECTED;
|
|
54
|
+
this.#logger.logError(this.#createLogMessage('connection failure'), error);
|
|
55
|
+
// The error isn't re-thrown to make it non-blocking, and let the monitoring do its work.
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
this.#connectPromise = undefined;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async #disconnect() {
|
|
62
|
+
this.#state = States.DISCONNECTING;
|
|
63
|
+
try {
|
|
64
|
+
this.#disconnectPromise = this.#connectable.disconnect();
|
|
65
|
+
await this.#disconnectPromise;
|
|
66
|
+
this.#state = States.DISCONNECTED;
|
|
67
|
+
this.#logger.logInfo(this.#createLogMessage('disconnected successfully'));
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
this.#state = States.CONNECTED;
|
|
71
|
+
this.#logger.logError(this.#createLogMessage('disconnection failure'), error);
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
this.#disconnectPromise = undefined;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
#startMonitoring() {
|
|
79
|
+
if (this.#monitorTimeout !== undefined) {
|
|
80
|
+
this.#logger.logWarn(this.#createLogMessage('monitoring already started'));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
this.#scheduleMonitoring();
|
|
84
|
+
this.#logger.logInfo(this.#createLogMessage('monitoring started'));
|
|
85
|
+
}
|
|
86
|
+
#scheduleMonitoring() {
|
|
87
|
+
this.#monitorTimeout = setTimeout(async () => {
|
|
88
|
+
await this.#monitorConnection();
|
|
89
|
+
this.#scheduleMonitoring();
|
|
90
|
+
}, this.#timeoutDuration);
|
|
91
|
+
}
|
|
92
|
+
#stopMonitoring() {
|
|
93
|
+
if (this.#monitorTimeout === undefined) {
|
|
94
|
+
this.#logger.logWarn(this.#createLogMessage('monitoring already stopped'));
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
clearTimeout(this.#monitorTimeout);
|
|
98
|
+
this.#monitorTimeout = undefined;
|
|
99
|
+
this.#logger.logInfo(this.#createLogMessage('monitoring stopped'));
|
|
100
|
+
}
|
|
101
|
+
async #monitorConnection() {
|
|
102
|
+
this.#logger.logDebug(this.#createLogMessage('monitoring connection'));
|
|
103
|
+
if (this.#connectable.connected) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (this.#connectPromise !== undefined) {
|
|
107
|
+
return this.#connectPromise;
|
|
108
|
+
}
|
|
109
|
+
this.#logger.logWarn(this.#createLogMessage('connection lost'));
|
|
110
|
+
this.#state = States.DISCONNECTED;
|
|
111
|
+
return this.#connect();
|
|
112
|
+
}
|
|
113
|
+
#createLogMessage(message) {
|
|
114
|
+
return `[CONNECTION][${this.#name}] ${message}`;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@theshelf/connection",
|
|
3
|
+
"private": false,
|
|
4
|
+
"version": "0.3.1",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"repository": {
|
|
7
|
+
"url": "git+https://github.com/MaskingTechnology/theshelf.git"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"clean": "rimraf dist",
|
|
12
|
+
"test": "vitest run",
|
|
13
|
+
"test-coverage": "vitest run --coverage",
|
|
14
|
+
"lint": "eslint",
|
|
15
|
+
"review": "npm run build && npm run lint && npm run test",
|
|
16
|
+
"prepublishOnly": "npm run clean && npm run build"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"README.md",
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
22
|
+
"types": "dist/index.d.ts",
|
|
23
|
+
"exports": "./dist/index.js",
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"@theshelf/logging": "^0.3.0"
|
|
26
|
+
}
|
|
27
|
+
}
|