te.js 2.2.0 → 2.2.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/te.js CHANGED
@@ -6,7 +6,6 @@ import corsMiddleware from './cors/index.js';
6
6
  import radarMiddleware from './radar/index.js';
7
7
 
8
8
  import targetRegistry from './server/targets/registry.js';
9
- import dbManager from './database/index.js';
10
9
 
11
10
  import { loadConfigFile, standardizeObj } from './utils/configuration.js';
12
11
 
@@ -14,10 +13,12 @@ import targetHandler from './server/handler.js';
14
13
  import {
15
14
  getErrorsLlmConfig,
16
15
  validateErrorsLlmAtTakeoff,
16
+ verifyLlmConnection,
17
17
  } from './utils/errors-llm-config.js';
18
18
  import path from 'node:path';
19
19
  import { pathToFileURL } from 'node:url';
20
20
  import { readFile } from 'node:fs/promises';
21
+ import { readFrameworkVersion, fmtMs, statusLine } from './utils/startup.js';
21
22
  import { findTargetFiles } from './utils/auto-register.js';
22
23
  import { registerDocRoutes } from './auto-docs/ui/docs-ui.js';
23
24
  import TejError from './server/error.js';
@@ -26,7 +27,8 @@ const logger = new TejLogger('Tejas');
26
27
 
27
28
  /**
28
29
  * Performs a graceful shutdown: closes the HTTP server (if started), then exits.
29
- * Invoked by process-level fatal error handlers.
30
+ * Invoked by process-level fatal error handlers. This is used to ensure that the server is closed properly
31
+ * when the process is terminated.
30
32
  * @param {number} [exitCode=1]
31
33
  */
32
34
  async function gracefulShutdown(exitCode = 1) {
@@ -70,7 +72,7 @@ process.on('uncaughtException', (error) => {
70
72
  * @class
71
73
  * @description
72
74
  * Tejas is a Node.js framework for building powerful backend services.
73
- * It provides features like routing, middleware support, database connections,
75
+ * It provides features like routing, middleware support,
74
76
  * and automatic target (route) registration.
75
77
  */
76
78
  class Tejas {
@@ -83,13 +85,6 @@ class Tejas {
83
85
  * @param {boolean} [args.log.http_requests] - Whether to log incoming HTTP requests
84
86
  * @param {boolean} [args.log.exceptions] - Whether to log exceptions
85
87
  * @param {Object} [args.db] - Database configuration options
86
- * @param {Object} [args.withRedis] - Redis connection configuration
87
- * @param {boolean} [args.withRedis.isCluster=false] - Whether to use Redis Cluster
88
- * @param {Object} [args.withRedis.socket] - Redis socket connection options
89
- * @param {string} [args.withRedis.socket.host] - Redis server hostname
90
- * @param {number} [args.withRedis.socket.port] - Redis server port
91
- * @param {boolean} [args.withRedis.socket.tls] - Whether to use TLS for connection
92
- * @param {string} [args.withRedis.url] - Redis connection URL (alternative to socket config)
93
88
  */
94
89
  constructor(args) {
95
90
  if (Tejas.instance) return Tejas.instance;
@@ -203,153 +198,70 @@ class Tejas {
203
198
  /**
204
199
  * Starts the Tejas server
205
200
  *
206
- * @param {Object} [options] - Server configuration options
207
- * @param {Object} [options.withRedis] - Redis connection options
208
- * @param {Object} [options.withMongo] - MongoDB connection options (https://www.mongodb.com/docs/drivers/node/current/fundamentals/connection/)
209
201
  * @description
210
202
  * Creates and starts an HTTP server on the configured port.
211
- * Optionally initializes Redis and/or MongoDB connections if configuration is provided.
212
- * For Redis, accepts cluster flag and all connection options supported by node-redis package.
213
- * For MongoDB, accepts all connection options supported by the official MongoDB Node.js driver.
214
203
  *
215
204
  * @example
216
205
  * const app = new Tejas();
217
- *
218
- * // Start server with Redis and MongoDB
219
- * app.takeoff({
220
- * withRedis: {
221
- * url: 'redis://alice:foobared@awesome.redis.server:6380',
222
- * isCluster: false
223
- * },
224
- * withMongo: { url: 'mongodb://localhost:27017/mydatabase' }
225
- * });
226
- *
227
- * // Start server with only Redis using defaults
228
- * app.takeoff({
229
- * withRedis: { url: 'redis://localhost:6379' }
230
- * });
231
- *
232
- * // Start server without databases
233
206
  * app.takeoff(); // Server starts on default port 1403
234
207
  */
235
- async takeoff({ withRedis, withMongo } = {}) {
208
+ async takeoff() {
209
+ const t0 = Date.now();
210
+
236
211
  // Load configuration first (async file read)
237
212
  await this.generateConfiguration();
238
213
 
214
+ // ── Startup banner ──────────────────────────────────────────────────
215
+ const version = await readFrameworkVersion();
216
+ const port = env('PORT');
217
+ const nodeEnv = process.env.NODE_ENV || 'development';
218
+ const banner = [
219
+ '',
220
+ ' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━',
221
+ ` Tejas v${version}`,
222
+ ` Port: ${port}`,
223
+ ` PID: ${process.pid}`,
224
+ ` Node: ${process.version}`,
225
+ ` Env: ${nodeEnv}`,
226
+ ' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━',
227
+ '',
228
+ ].join('\n');
229
+ process.stdout.write(banner + '\n');
230
+
231
+ // ── Live feature status ────────────────────────────────────────────
232
+ const line = statusLine(process.stdout.isTTY);
233
+
234
+ if (this._radarStatus) {
235
+ const s = this._radarStatus;
236
+ line.finish(s.feature, s.ok, s.detail);
237
+ }
238
+
239
239
  validateErrorsLlmAtTakeoff();
240
240
  const errorsLlm = getErrorsLlmConfig();
241
241
  if (errorsLlm.enabled) {
242
- logger.info(
243
- `errors.llm enabled successfully — baseURL: ${errorsLlm.baseURL}, model: ${errorsLlm.model}, messageType: ${errorsLlm.messageType}, apiKey: ${errorsLlm.apiKey ? '***' : '(missing)'}`,
244
- );
242
+ if (errorsLlm.verifyOnStart) {
243
+ line.start('LLM Errors', 'verifying model...');
244
+ const result = await verifyLlmConnection();
245
+ line.finish('LLM Errors', result.status.ok, result.status.detail);
246
+ } else {
247
+ line.finish(
248
+ 'LLM Errors',
249
+ true,
250
+ `enabled (${errorsLlm.model || 'default model'}, mode: ${errorsLlm.mode})`,
251
+ );
252
+ }
245
253
  }
246
254
 
247
- // Register target files before the server starts listening so no request
248
- // can arrive before all routes are fully registered.
249
255
  await this.registerTargetsDir();
250
256
 
257
+ // ── Start HTTP server ───────────────────────────────────────────────
251
258
  this.engine = createServer(targetHandler);
252
- this.engine.listen(env('PORT'), async () => {
253
- logger.info(`Took off from port ${env('PORT')}`);
254
-
255
- if (withRedis) await this.withRedis(withRedis);
256
- if (withMongo) await this.withMongo(withMongo);
257
- });
258
-
259
- this.engine.on('error', (err) => {
260
- logger.error(`Server error: ${err}`);
261
- });
262
- }
263
-
264
- /**
265
- * Initializes a Redis connection
266
- *
267
- * @param {Object} [config] - Redis connection configuration
268
- * @param {boolean} [config.isCluster=false] - Whether to use Redis Cluster
269
- * @param {Object} [config.socket] - Redis socket connection options
270
- * @param {string} [config.socket.host] - Redis server hostname
271
- * @param {number} [config.socket.port] - Redis server port
272
- * @param {boolean} [config.socket.tls] - Whether to use TLS for connection
273
- * @param {string} [config.url] - Redis connection URL (alternative to socket config)
274
- * @returns {Promise<Tejas>} Returns a Promise that resolves to this instance for chaining
275
- *
276
- * @example
277
- * // Initialize Redis with URL
278
- * await app.withRedis({
279
- * url: 'redis://localhost:6379'
280
- * }).withRateLimit({
281
- * maxRequests: 100,
282
- * store: 'redis'
283
- * });
284
- *
285
- * @example
286
- * // Initialize Redis with socket options
287
- * await app.withRedis({
288
- * socket: {
289
- * host: 'localhost',
290
- * port: 6379
291
- * }
292
- * });
293
- */
294
- async withRedis(config) {
295
- if (config) {
296
- await dbManager.initializeConnection('redis', config);
297
- } else {
298
- logger.warn(
299
- 'No Redis configuration provided. Skipping Redis connection.',
300
- );
301
- }
302
-
303
- return this;
304
- }
259
+ await new Promise((resolve) => this.engine.listen(port, resolve));
260
+ this.engine.on('error', (err) => logger.error(`Server error: ${err}`));
305
261
 
306
- /**
307
- * Initializes a MongoDB connection
308
- *
309
- * @param {Object} [config] - MongoDB connection configuration
310
- * @param {string} [config.uri] - MongoDB connection URI
311
- * @param {Object} [config.options] - Additional MongoDB connection options
312
- * @returns {Tejas} Returns a Promise that resolves to this instance for chaining
313
- *
314
- * @example
315
- * // Initialize MongoDB with URI
316
- * await app.withMongo({
317
- * uri: 'mongodb://localhost:27017/myapp'
318
- * });
319
- *
320
- * @example
321
- * // Initialize MongoDB with options
322
- * await app.withMongo({
323
- * uri: 'mongodb://localhost:27017/myapp',
324
- * options: {
325
- * useNewUrlParser: true,
326
- * useUnifiedTopology: true
327
- * }
328
- * });
329
- *
330
- * @example
331
- * // Chain database connections
332
- * await app
333
- * .withMongo({
334
- * uri: 'mongodb://localhost:27017/myapp'
335
- * })
336
- * .withRedis({
337
- * url: 'redis://localhost:6379'
338
- * })
339
- * .withRateLimit({
340
- * maxRequests: 100,
341
- * store: 'redis'
342
- * });
343
- */
344
- withMongo(config) {
345
- if (config) {
346
- dbManager.initializeConnection('mongodb', config);
347
- } else {
348
- logger.warn(
349
- 'No MongoDB configuration provided. Skipping MongoDB connection.',
350
- );
351
- }
352
- return this;
262
+ process.stdout.write(
263
+ `\n \x1b[32m\u2708 Ready on port ${port} in ${fmtMs(Date.now() - t0)}\x1b[0m\n\n`,
264
+ );
353
265
  }
354
266
 
355
267
  /**
@@ -369,6 +281,7 @@ class Tejas {
369
281
  * @param {number} [config.rateLimit] - Max LLM calls per minute across all requests (default 10)
370
282
  * @param {boolean} [config.cache] - Cache LLM results by throw site + error message to avoid repeated calls (default true)
371
283
  * @param {number} [config.cacheTTL] - How long cached results are reused in milliseconds (default 3600000 = 1 hour)
284
+ * @param {boolean} [config.verifyOnStart] - Send a test prompt to the LLM at startup to verify connectivity (default false)
372
285
  * @returns {Tejas} The Tejas instance for chaining
373
286
  *
374
287
  * @example
@@ -400,6 +313,8 @@ class Tejas {
400
313
  if (config.cache != null) setEnv('ERRORS_LLM_CACHE', config.cache);
401
314
  if (config.cacheTTL != null)
402
315
  setEnv('ERRORS_LLM_CACHE_TTL', config.cacheTTL);
316
+ if (config.verifyOnStart != null)
317
+ setEnv('ERRORS_LLM_VERIFY_ON_START', config.verifyOnStart);
403
318
  }
404
319
  return this;
405
320
  }
@@ -411,8 +326,10 @@ class Tejas {
411
326
  * @param {number} [config.maxRequests=60] - Maximum number of requests allowed in the time window
412
327
  * @param {number} [config.timeWindowSeconds=60] - Time window in seconds
413
328
  * @param {string} [config.algorithm='sliding-window'] - Rate-limiting algorithm ('token-bucket', 'sliding-window', or 'fixed-window')
329
+ * @param {string|Object} [config.store='memory'] - Storage backend: 'memory' (default) or
330
+ * { type: 'redis', url: 'redis://...', ...redisOptions } for distributed deployments.
331
+ * In-memory storage is not shared across processes and may be inaccurate in distributed setups.
414
332
  * @param {Object} [config.algorithmOptions] - Algorithm-specific options
415
- * @param {Object} [config.redis] - Redis configuration for distributed rate limiting
416
333
  * @param {Function} [config.keyGenerator] - Function to generate unique identifiers (defaults to IP-based)
417
334
  * @param {Object} [config.headerFormat] - Rate limit header format configuration
418
335
  * @returns {Tejas} The Tejas instance for chaining
@@ -498,10 +415,10 @@ class Tejas {
498
415
  * Note: the collector enforces its own non-bypassable masking
499
416
  * layer server-side regardless of this setting.
500
417
  *
501
- * @returns {Tejas} The Tejas instance for chaining
418
+ * @returns {Promise<Tejas>} The Tejas instance for chaining
502
419
  *
503
420
  * @example
504
- * app.withRadar({ apiKey: process.env.RADAR_API_KEY });
421
+ * await app.withRadar({ apiKey: process.env.RADAR_API_KEY });
505
422
  * app.takeoff();
506
423
  *
507
424
  * @example
@@ -526,8 +443,12 @@ class Tejas {
526
443
  * },
527
444
  * });
528
445
  */
529
- withRadar(config = {}) {
530
- this.midair(radarMiddleware(config));
446
+ async withRadar(config = {}) {
447
+ const mw = await radarMiddleware(config);
448
+ if (mw._radarStatus) {
449
+ this._radarStatus = mw._radarStatus;
450
+ }
451
+ this.midair(mw);
531
452
  return this;
532
453
  }
533
454
 
@@ -68,6 +68,7 @@ function normalizeChannel(v) {
68
68
  * rateLimit: number,
69
69
  * cache: boolean,
70
70
  * cacheTTL: number,
71
+ * verifyOnStart: boolean,
71
72
  * }}
72
73
  */
73
74
  export function getErrorsLlmConfig() {
@@ -137,6 +138,13 @@ export function getErrorsLlmConfig() {
137
138
  ? 3600000
138
139
  : cacheTTLNum;
139
140
 
141
+ const verifyOnStartRaw = env('ERRORS_LLM_VERIFY_ON_START') ?? '';
142
+ const verifyOnStart =
143
+ verifyOnStartRaw === true ||
144
+ verifyOnStartRaw === 'true' ||
145
+ verifyOnStartRaw === '1' ||
146
+ verifyOnStartRaw === 1;
147
+
140
148
  return Object.freeze({
141
149
  enabled: Boolean(enabled),
142
150
  baseURL: String(baseURL ?? '').trim(),
@@ -150,11 +158,66 @@ export function getErrorsLlmConfig() {
150
158
  rateLimit,
151
159
  cache,
152
160
  cacheTTL,
161
+ verifyOnStart,
153
162
  });
154
163
  }
155
164
 
156
165
  export { MESSAGE_TYPES, LLM_MODES, LLM_CHANNELS };
157
166
 
167
+ /**
168
+ * Fire a lightweight probe to the configured LLM provider and verify it
169
+ * responds correctly. Intended to run once at takeoff when `verifyOnStart: true`.
170
+ *
171
+ * Never throws — a flaky provider does not prevent the server from starting.
172
+ *
173
+ * @returns {Promise<{ ok: boolean, status: { feature: string, ok: boolean, detail: string } }>}
174
+ */
175
+ export async function verifyLlmConnection() {
176
+ const { baseURL, apiKey, model, timeout, mode } = getErrorsLlmConfig();
177
+
178
+ const { createProvider } = await import('../lib/llm/index.js');
179
+ const provider = createProvider({ baseURL, apiKey, model, timeout });
180
+
181
+ const shortModel = model.split('/').pop().split(':')[0] || model;
182
+ const start = Date.now();
183
+ try {
184
+ const { content } = await provider.analyze(
185
+ 'Respond with only the JSON object {"status":"ok"}. No explanation.',
186
+ );
187
+ const elapsed = Date.now() - start;
188
+
189
+ if (content.includes('"ok"')) {
190
+ return {
191
+ ok: true,
192
+ status: {
193
+ feature: 'LLM Errors',
194
+ ok: true,
195
+ detail: `verified (${shortModel}, ${elapsed}ms, mode: ${mode})`,
196
+ },
197
+ };
198
+ }
199
+
200
+ return {
201
+ ok: false,
202
+ status: {
203
+ feature: 'LLM Errors',
204
+ ok: false,
205
+ detail: `unexpected response from ${shortModel} (${elapsed}ms)`,
206
+ },
207
+ };
208
+ } catch (err) {
209
+ const elapsed = Date.now() - start;
210
+ return {
211
+ ok: false,
212
+ status: {
213
+ feature: 'LLM Errors',
214
+ ok: false,
215
+ detail: `${err.message} (${elapsed}ms)`,
216
+ },
217
+ };
218
+ }
219
+ }
220
+
158
221
  /**
159
222
  * Validate errors.llm when enabled: require baseURL, apiKey, and model (after LLM_ fallback).
160
223
  * Also warns about misconfigurations (e.g. channel set with sync mode).
@@ -0,0 +1,80 @@
1
+ import path from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { readFile } from 'node:fs/promises';
4
+
5
+ /**
6
+ * Read the framework's own package.json version.
7
+ * Resolves relative to the framework source, not the user's app.
8
+ * @returns {Promise<string>}
9
+ */
10
+ export async function readFrameworkVersion() {
11
+ try {
12
+ const dir = path.dirname(fileURLToPath(import.meta.url));
13
+ const raw = await readFile(path.join(dir, '..', 'package.json'), 'utf8');
14
+ return JSON.parse(raw).version ?? 'unknown';
15
+ } catch {
16
+ return 'unknown';
17
+ }
18
+ }
19
+
20
+ /** Format milliseconds into a compact human-readable string. */
21
+ export function fmtMs(ms) {
22
+ return ms >= 1000 ? `${(ms / 1000).toFixed(1)}s` : `${Math.round(ms)}ms`;
23
+ }
24
+
25
+ const SPINNER = [
26
+ '\u280B',
27
+ '\u2819',
28
+ '\u2839',
29
+ '\u2838',
30
+ '\u283C',
31
+ '\u2834',
32
+ '\u2826',
33
+ '\u2827',
34
+ '\u2807',
35
+ '\u280F',
36
+ ];
37
+
38
+ /**
39
+ * Create a live status line writer for startup progress.
40
+ * On TTY terminals, shows a spinning animation that gets overwritten in-place
41
+ * when the step completes. On non-TTY (piped, CI), only prints the final result.
42
+ * @param {boolean} isTTY
43
+ */
44
+ export function statusLine(isTTY) {
45
+ let timer = null;
46
+
47
+ function stop() {
48
+ if (timer) {
49
+ clearInterval(timer);
50
+ timer = null;
51
+ }
52
+ }
53
+
54
+ return {
55
+ start(feature, message) {
56
+ if (!isTTY) return;
57
+ let frame = 0;
58
+ const render = () => {
59
+ const s = SPINNER[frame % SPINNER.length];
60
+ process.stdout.write(
61
+ `\r\x1b[K ${feature.padEnd(14)} \x1b[36m${s}\x1b[0m \x1b[2m${message}\x1b[0m`,
62
+ );
63
+ frame++;
64
+ };
65
+ render();
66
+ timer = setInterval(render, 80);
67
+ },
68
+ finish(feature, ok, detail) {
69
+ stop();
70
+ if (isTTY) process.stdout.write('\r\x1b[K');
71
+ const icon =
72
+ ok === true
73
+ ? '\x1b[32m\u2713\x1b[0m'
74
+ : ok === false
75
+ ? '\x1b[31m\u2717\x1b[0m'
76
+ : '\x1b[2m\u2014\x1b[0m';
77
+ process.stdout.write(` ${feature.padEnd(14)} ${icon} ${detail}\n`);
78
+ },
79
+ };
80
+ }
package/database/index.js DELETED
@@ -1,167 +0,0 @@
1
- import redis from './redis.js';
2
- import mongodb from './mongodb.js';
3
- import TejError from '../server/error.js';
4
- import TejLogger from 'tej-logger';
5
-
6
- const logger = new TejLogger('DatabaseManager');
7
-
8
- class DatabaseManager {
9
- static #instance = null;
10
- static #isInitializing = false;
11
-
12
- // Enhanced connection tracking with metadata
13
- #connections = new Map();
14
- #initializingConnections = new Map();
15
-
16
- // Helper method for sleeping
17
- async #sleep(ms) {
18
- const { promise, resolve } = Promise.withResolvers();
19
- setTimeout(resolve, ms);
20
- return promise;
21
- }
22
-
23
- constructor() {
24
- if (DatabaseManager.#instance) {
25
- return DatabaseManager.#instance;
26
- }
27
-
28
- if (!DatabaseManager.#isInitializing) {
29
- throw new TejError(
30
- 500,
31
- 'Use DatabaseManager.getInstance() to get the instance',
32
- );
33
- }
34
-
35
- DatabaseManager.#isInitializing = false;
36
- DatabaseManager.#instance = this;
37
- }
38
-
39
- static getInstance() {
40
- if (!DatabaseManager.#instance) {
41
- DatabaseManager.#isInitializing = true;
42
- DatabaseManager.#instance = new DatabaseManager();
43
- }
44
- return DatabaseManager.#instance;
45
- }
46
-
47
- async initializeConnection(dbType, config) {
48
- const key = dbType.toLowerCase();
49
-
50
- // If a connection already exists for this config, return it
51
- if (this.#connections.has(key)) {
52
- return this.#connections.get(key).client;
53
- }
54
-
55
- // Set initializing flag
56
- this.#initializingConnections.set(key, true);
57
-
58
- let client;
59
- try {
60
- switch (key) {
61
- case 'redis':
62
- client = await redis.createConnection({
63
- isCluster: config.isCluster || false,
64
- options: config || {},
65
- });
66
- break;
67
- case 'mongodb':
68
- client = await mongodb.createConnection(config);
69
- break;
70
- default:
71
- throw new TejError(400, `Unsupported database type: ${dbType}`);
72
- }
73
-
74
- this.#connections.set(key, {
75
- type: dbType,
76
- client,
77
- config,
78
- });
79
-
80
- // Clear initializing flag
81
- this.#initializingConnections.delete(key);
82
-
83
- return client;
84
- } catch (error) {
85
- // Clear initializing flag on error
86
- this.#initializingConnections.delete(key);
87
- logger.error(`Failed to initialize ${dbType} connection:`, error);
88
- throw error;
89
- }
90
- }
91
-
92
- getConnection(dbType) {
93
- const key = dbType.toLowerCase();
94
- const connection = this.#connections.get(key);
95
- if (!connection) {
96
- throw new TejError(
97
- 404,
98
- `No connection found for ${dbType} with given config`,
99
- );
100
- }
101
- return connection.client;
102
- }
103
-
104
- async closeConnection(dbType, config) {
105
- const key = dbType.toLowerCase();
106
- if (!this.#connections.has(key)) {
107
- return;
108
- }
109
-
110
- try {
111
- const connection = this.#connections.get(key);
112
- switch (key) {
113
- case 'redis':
114
- await redis.closeConnection(connection.client);
115
- break;
116
- case 'mongodb':
117
- await mongodb.closeConnection(connection.client);
118
- break;
119
- }
120
-
121
- this.#connections.delete(key);
122
- } catch (error) {
123
- logger.error(`Error closing ${dbType} connection:`, error);
124
- throw error;
125
- }
126
- }
127
-
128
- /**
129
- * Close all database connections
130
- * @returns {Promise<void>}
131
- */
132
- async closeAllConnections() {
133
- const closePromises = [];
134
- for (const [key, connection] of this.#connections) {
135
- closePromises.push(
136
- this.closeConnection(connection.type, connection.config),
137
- );
138
- }
139
- await Promise.all(closePromises);
140
- this.#connections.clear();
141
- }
142
-
143
- /**
144
- * Get all active connections
145
- * @returns {Map<string, {type: string, client: any, config: Object}>}
146
- */
147
- getActiveConnections() {
148
- return new Map(this.#connections);
149
- }
150
-
151
- /**
152
- * Check if a connection exists or is being initialized
153
- * @param {string} dbType - Type of database
154
- * @param {Object} config - Database configuration
155
- * @returns {{ exists: boolean, initializing: boolean }}
156
- */
157
- hasConnection(dbType, config) {
158
- const key = dbType.toLowerCase();
159
- return {
160
- exists: this.#connections.has(key),
161
- initializing: this.#initializingConnections.has(key),
162
- };
163
- }
164
- }
165
-
166
- const dbManager = DatabaseManager.getInstance();
167
- export default dbManager;