@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 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,7 @@
1
+ export declare const States: {
2
+ readonly DISCONNECTED: "DISCONNECTED";
3
+ readonly CONNECTING: "CONNECTING";
4
+ readonly CONNECTED: "CONNECTED";
5
+ readonly DISCONNECTING: "DISCONNECTING";
6
+ };
7
+ export type State = typeof States[keyof typeof States];
@@ -0,0 +1,6 @@
1
+ export const States = {
2
+ DISCONNECTED: 'DISCONNECTED',
3
+ CONNECTING: 'CONNECTING',
4
+ CONNECTED: 'CONNECTED',
5
+ DISCONNECTING: 'DISCONNECTING'
6
+ };
@@ -0,0 +1,5 @@
1
+ export interface Connectable {
2
+ get connected(): boolean;
3
+ connect(): Promise<void>;
4
+ disconnect(): Promise<void>;
5
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,4 @@
1
+ export * from './definitions/constants.js';
2
+ export type * from './definitions/constants.js';
3
+ export type * from './definitions/interfaces.js';
4
+ export { default } from './ConnectionManager.js';
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export * from './definitions/constants.js';
2
+ export { default } from './ConnectionManager.js';
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
+ }