UNPKG

8.74 kBJavaScriptView Raw
1"use strict";
2/*
3 * @adonisjs/lucid
4 *
5 * (c) Harminder Virk <virk@adonisjs.com>
6 *
7 * For the full copyright and license information, please view the LICENSE
8 * file that was distributed with this source code.
9 */
10Object.defineProperty(exports, "__esModule", { value: true });
11exports.ConnectionManager = void 0;
12/// <reference path="../../adonis-typings/index.ts" />
13const utils_1 = require("@poppinss/utils");
14const index_1 = require("./index");
15/**
16 * Connection manager job is to manage multiple named connections. You can add any number
17 * or connections by registering their config only once and then make use of `connect`
18 * and `close` methods to create and destroy db connections.
19 */
20class ConnectionManager {
21 constructor(logger, emitter) {
22 Object.defineProperty(this, "logger", {
23 enumerable: true,
24 configurable: true,
25 writable: true,
26 value: logger
27 });
28 Object.defineProperty(this, "emitter", {
29 enumerable: true,
30 configurable: true,
31 writable: true,
32 value: emitter
33 });
34 /**
35 * List of managed connections
36 */
37 Object.defineProperty(this, "connections", {
38 enumerable: true,
39 configurable: true,
40 writable: true,
41 value: new Map()
42 });
43 /**
44 * Connections for which the config was patched. They must get removed
45 * overtime, unless application is behaving unstable.
46 */
47 Object.defineProperty(this, "orphanConnections", {
48 enumerable: true,
49 configurable: true,
50 writable: true,
51 value: new Set()
52 });
53 }
54 /**
55 * Handles disconnection of a connection
56 */
57 handleDisconnect(connection) {
58 /**
59 * We received the close event on the orphan connection and not the connection
60 * that is in use
61 */
62 if (this.orphanConnections.has(connection)) {
63 this.orphanConnections.delete(connection);
64 this.emitter.emit('db:connection:disconnect', connection);
65 this.logger.trace({ connection: connection.name }, 'disconnecting connection inside manager');
66 return;
67 }
68 const internalConnection = this.get(connection.name);
69 /**
70 * This will be false, when connection was released at the
71 * time of closing
72 */
73 if (!internalConnection) {
74 return;
75 }
76 this.emitter.emit('db:connection:disconnect', connection);
77 this.logger.trace({ connection: connection.name }, 'disconnecting connection inside manager');
78 delete internalConnection.connection;
79 internalConnection.state = 'closed';
80 }
81 /**
82 * Handles event when a new connection is added
83 */
84 handleConnect(connection) {
85 const internalConnection = this.get(connection.name);
86 if (!internalConnection) {
87 return;
88 }
89 this.emitter.emit('db:connection:connect', connection);
90 internalConnection.state = 'open';
91 }
92 /**
93 * Monitors a given connection by listening for lifecycle events
94 */
95 monitorConnection(connection) {
96 connection.on('disconnect', ($connection) => this.handleDisconnect($connection));
97 connection.on('connect', ($connection) => this.handleConnect($connection));
98 connection.on('error', (error, $connection) => {
99 this.emitter.emit('db:connection:error', [error, $connection]);
100 });
101 }
102 /**
103 * Add a named connection with it's configuration. Make sure to call `connect`
104 * before using the connection to make database queries.
105 */
106 add(connectionName, config) {
107 /**
108 * Noop when connection already exists. If one wants to change the config, they
109 * must release the old connection and add a new one
110 */
111 if (this.has(connectionName)) {
112 return;
113 }
114 this.logger.trace({ connection: connectionName }, 'adding new connection to the manager');
115 this.connections.set(connectionName, {
116 name: connectionName,
117 config: config,
118 state: 'registered',
119 });
120 }
121 /**
122 * Connect to the database using config for a given named connection
123 */
124 connect(connectionName) {
125 const connection = this.connections.get(connectionName);
126 if (!connection) {
127 throw new utils_1.Exception(`Cannot connect to unregistered connection ${connectionName}`, 500, 'E_UNMANAGED_DB_CONNECTION');
128 }
129 /**
130 * Ignore when the there is already a connection.
131 */
132 if (this.isConnected(connection.name)) {
133 return;
134 }
135 /**
136 * Create a new connection and monitor it's state
137 */
138 connection.connection = new index_1.Connection(connection.name, connection.config, this.logger);
139 this.monitorConnection(connection.connection);
140 connection.connection.connect();
141 }
142 /**
143 * Patching the config
144 */
145 patch(connectionName, config) {
146 const connection = this.get(connectionName);
147 /**
148 * If connection is missing, then simply add it
149 */
150 if (!connection) {
151 return this.add(connectionName, config);
152 }
153 /**
154 * Move the current connection to the orphan connections. We need
155 * to keep a seperate track of old connections to make sure
156 * they cleanup after some time
157 */
158 if (connection.connection) {
159 this.orphanConnections.add(connection.connection);
160 connection.connection.disconnect();
161 }
162 /**
163 * Updating config and state. Next call to connect will use the
164 * new config
165 */
166 connection.state = 'migrating';
167 connection.config = config;
168 /**
169 * Removing the connection right away, so that the next call to `connect`
170 * creates a new one with new config
171 */
172 delete connection.connection;
173 }
174 /**
175 * Returns the connection node for a given named connection
176 */
177 get(connectionName) {
178 return this.connections.get(connectionName);
179 }
180 /**
181 * Returns a boolean telling if we have connection details for
182 * a given named connection. This method doesn't tell if
183 * connection is connected or not.
184 */
185 has(connectionName) {
186 return this.connections.has(connectionName);
187 }
188 /**
189 * Returns a boolean telling if connection has been established
190 * with the database or not
191 */
192 isConnected(connectionName) {
193 if (!this.has(connectionName)) {
194 return false;
195 }
196 const connection = this.get(connectionName);
197 return !!connection.connection && connection.state === 'open';
198 }
199 /**
200 * Closes a given connection and can optionally release it from the
201 * tracking list
202 */
203 async close(connectionName, release = false) {
204 if (this.isConnected(connectionName)) {
205 const connection = this.get(connectionName);
206 await connection.connection.disconnect();
207 connection.state = 'closing';
208 }
209 if (release) {
210 await this.release(connectionName);
211 }
212 }
213 /**
214 * Close all tracked connections
215 */
216 async closeAll(release = false) {
217 await Promise.all(Array.from(this.connections.keys()).map((name) => this.close(name, release)));
218 }
219 /**
220 * Release a connection. This will disconnect the connection
221 * and will delete it from internal list
222 */
223 async release(connectionName) {
224 if (this.isConnected(connectionName)) {
225 await this.close(connectionName, true);
226 }
227 else {
228 this.connections.delete(connectionName);
229 }
230 }
231 /**
232 * Returns the report for all the connections marked for healthChecks.
233 */
234 async report() {
235 const reports = await Promise.all(Array.from(this.connections.keys())
236 .filter((one) => this.get(one).config.healthCheck)
237 .map((one) => {
238 this.connect(one);
239 return this.get(one).connection.getReport();
240 }));
241 const healthy = !reports.find((report) => !!report.error);
242 return {
243 displayName: 'Database',
244 health: {
245 healthy,
246 message: healthy
247 ? 'All connections are healthy'
248 : 'One or more connections are not healthy',
249 },
250 meta: reports,
251 };
252 }
253}
254exports.ConnectionManager = ConnectionManager;