@coherent.js/database 1.0.0-beta.5 → 1.0.0-beta.6

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/dist/index.js CHANGED
@@ -1158,431 +1158,6 @@ init_migration();
1158
1158
 
1159
1159
  // src/connection-manager.js
1160
1160
  import { EventEmitter } from "events";
1161
- var DatabaseManager = class extends EventEmitter {
1162
- constructor(config) {
1163
- super();
1164
- this.config = this.validateConfig(config);
1165
- this.adapter = null;
1166
- this.pool = null;
1167
- this.isConnected = false;
1168
- this.connectionAttempts = 0;
1169
- this.maxRetries = 3;
1170
- this.healthCheckInterval = null;
1171
- this.healthCheckFrequency = 3e4;
1172
- this.stats = {
1173
- totalConnections: 0,
1174
- activeConnections: 0,
1175
- failedConnections: 0,
1176
- queriesExecuted: 0,
1177
- averageQueryTime: 0,
1178
- lastHealthCheck: null
1179
- };
1180
- }
1181
- /**
1182
- * Validate database configuration
1183
- *
1184
- * @private
1185
- * @param {Object} config - Configuration to validate
1186
- * @returns {Object} Validated configuration
1187
- * @throws {Error} If configuration is invalid
1188
- */
1189
- validateConfig(config) {
1190
- if (!config || typeof config !== "object") {
1191
- throw new Error("Database configuration is required");
1192
- }
1193
- if (config.adapter) {
1194
- if (typeof config.adapter !== "object" || typeof config.adapter.createPool !== "function") {
1195
- throw new Error("Invalid adapter provided. Adapter must be an object with a createPool method");
1196
- }
1197
- if (!config.store) {
1198
- config.store = { name: "default" };
1199
- } else if (typeof config.store === "string") {
1200
- config.store = { name: config.store };
1201
- }
1202
- return config;
1203
- }
1204
- const { type, database } = config;
1205
- if (!type) {
1206
- throw new Error("Either database type or adapter is required");
1207
- }
1208
- const supportedTypes = ["postgresql", "mysql", "sqlite", "mongodb"];
1209
- if (!supportedTypes.includes(type)) {
1210
- throw new Error(`Unsupported database type: ${type}. Supported types: ${supportedTypes.join(", ")}`);
1211
- }
1212
- if (!database) {
1213
- throw new Error("Database name is required for type-based configuration");
1214
- }
1215
- const defaultPorts = {
1216
- postgresql: 5432,
1217
- mysql: 3306,
1218
- mongodb: 27017,
1219
- sqlite: null
1220
- };
1221
- return {
1222
- host: config.host || "localhost",
1223
- port: config.port || defaultPorts[type],
1224
- ...config,
1225
- pool: {
1226
- min: 2,
1227
- max: 10,
1228
- acquireTimeoutMillis: 3e4,
1229
- createTimeoutMillis: 3e4,
1230
- destroyTimeoutMillis: 5e3,
1231
- idleTimeoutMillis: 3e4,
1232
- reapIntervalMillis: 1e3,
1233
- createRetryIntervalMillis: 200,
1234
- ...config.pool
1235
- }
1236
- };
1237
- }
1238
- /**
1239
- * Connect to the database
1240
- *
1241
- * @returns {Promise<void>}
1242
- * @throws {Error} If connection fails after retries
1243
- *
1244
- * @example
1245
- * await db.connect();
1246
- * console.log('Database connected successfully');
1247
- */
1248
- async connect() {
1249
- if (this.isConnected) {
1250
- return;
1251
- }
1252
- try {
1253
- this.adapter = await this.loadAdapter(this.config.type);
1254
- this.pool = await this.adapter.createPool(this.config);
1255
- await this.testConnection();
1256
- this.isConnected = true;
1257
- this.connectionAttempts = 0;
1258
- if (this.adapter.startHealthChecks) {
1259
- this.startHealthChecks();
1260
- }
1261
- return this;
1262
- } catch (_error) {
1263
- this.connectionAttempts++;
1264
- this.stats.failedConnections++;
1265
- this.emit("_error", _error);
1266
- if (this.connectionAttempts < this.maxRetries) {
1267
- console.warn(`Connection attempt ${this.connectionAttempts} failed. Retrying in 2 seconds...`);
1268
- await new Promise((resolve) => setTimeout(resolve, 2e3));
1269
- return this.connect();
1270
- }
1271
- throw new Error(`Failed to connect to database after ${this.connectionAttempts} attempts: ${_error.message}`);
1272
- }
1273
- }
1274
- /**
1275
- * Load database adapter
1276
- *
1277
- * @private
1278
- * @param {string} type - Database type
1279
- * @returns {Object} Database adapter
1280
- */
1281
- loadAdapter(type) {
1282
- if (this.config.adapter) {
1283
- return this.config.adapter;
1284
- }
1285
- const adapterMap = {
1286
- postgresql: "./adapters/postgresql.js",
1287
- mysql: "./adapters/mysql.js",
1288
- sqlite: "./adapters/sqlite.js",
1289
- mongodb: "./adapters/mongodb.js",
1290
- memory: "./adapters/memory.js"
1291
- };
1292
- const adapterPath = adapterMap[type];
1293
- if (!adapterPath) {
1294
- throw new Error(`No adapter found for database type: ${type}`);
1295
- }
1296
- return import(adapterPath).then((adapterModule) => {
1297
- if (adapterModule.default) {
1298
- return new adapterModule.default();
1299
- }
1300
- const AdapterClass = adapterModule[`${type.charAt(0).toUpperCase() + type.slice(1)}Adapter`];
1301
- if (AdapterClass) {
1302
- return new AdapterClass();
1303
- }
1304
- throw new Error(`No valid adapter found in ${adapterPath}`);
1305
- }).catch((_error) => {
1306
- throw new Error(`Failed to load ${type} adapter: ${_error.message}`);
1307
- });
1308
- }
1309
- /**
1310
- * Test database connection
1311
- *
1312
- * @private
1313
- * @returns {Promise<void>}
1314
- */
1315
- async testConnection() {
1316
- const startTime = Date.now();
1317
- try {
1318
- if (typeof this.adapter.testConnection === "function") {
1319
- await this.adapter.testConnection();
1320
- } else if (this.adapter.ping) {
1321
- await this.adapter.ping();
1322
- }
1323
- const duration = Date.now() - startTime;
1324
- this.stats.lastHealthCheck = /* @__PURE__ */ new Date();
1325
- this.emit("connect:test", { duration });
1326
- } catch (_error) {
1327
- this.emit("_error", _error);
1328
- throw new Error(`Database connection test failed: ${_error.message}`);
1329
- }
1330
- }
1331
- /**
1332
- * Execute a database query
1333
- *
1334
- * @param {string} sql - SQL query string
1335
- * @param {Array} [params=[]] - Query parameters
1336
- * @param {Object} [options={}] - Query options
1337
- * @returns {Promise<Object>} Query result
1338
- *
1339
- * @example
1340
- * const users = await db.query('SELECT * FROM users WHERE age > ?', [18]);
1341
- * const user = await db.query('SELECT * FROM users WHERE id = ?', [123], { single: true });
1342
- */
1343
- async query(operation, params = {}) {
1344
- if (!this.isConnected) {
1345
- throw new Error("Database not connected. Call connect() first.");
1346
- }
1347
- const startTime = Date.now();
1348
- try {
1349
- let result;
1350
- if (typeof this.pool.query === "function") {
1351
- result = await this.pool.query(operation, params);
1352
- } else if (typeof this.adapter.query === "function") {
1353
- result = await this.adapter.query(this.pool, operation, params);
1354
- } else {
1355
- throw new Error("No valid query method found on adapter or pool");
1356
- }
1357
- const duration = Date.now() - startTime;
1358
- this.stats.queriesExecuted++;
1359
- this.stats.averageQueryTime = (this.stats.averageQueryTime * (this.stats.queriesExecuted - 1) + duration) / this.stats.queriesExecuted;
1360
- if (this.config.debug) {
1361
- console.log(`Query executed in ${duration}ms: ${operation}`, params);
1362
- }
1363
- this.emit("query", { operation, params, duration });
1364
- return result;
1365
- } catch (_error) {
1366
- const duration = Date.now() - startTime;
1367
- this.emit("queryError", { operation, params, duration, _error: _error.message });
1368
- throw new Error(`Query failed: ${_error.message}`);
1369
- }
1370
- }
1371
- /**
1372
- * Start a database transaction
1373
- *
1374
- * @returns {Promise<Object>} Transaction object
1375
- *
1376
- * @example
1377
- * const tx = await db.transaction();
1378
- * try {
1379
- * await tx.query('INSERT INTO users (name) VALUES (?)', ['John']);
1380
- * await tx.query('INSERT INTO profiles (user_id) VALUES (?)', [userId]);
1381
- * await tx.commit();
1382
- * } catch (_error) {
1383
- * await tx.rollback();
1384
- * throw _error;
1385
- * }
1386
- */
1387
- async transaction() {
1388
- if (!this.isConnected) {
1389
- throw new Error("Database not connected. Call connect() first.");
1390
- }
1391
- return await this.adapter.transaction(this.pool);
1392
- }
1393
- /**
1394
- * Start health check monitoring
1395
- *
1396
- * @private
1397
- */
1398
- startHealthCheck() {
1399
- if (this.healthCheckInterval) {
1400
- return;
1401
- }
1402
- this.healthCheckInterval = setInterval(async () => {
1403
- try {
1404
- await this.testConnection();
1405
- this.emit("healthCheck", { status: "healthy", timestamp: /* @__PURE__ */ new Date() });
1406
- } catch (_error) {
1407
- this.emit("healthCheck", { status: "unhealthy", _error: _error.message, timestamp: /* @__PURE__ */ new Date() });
1408
- if (this.config.debug) {
1409
- console.error("Database health check failed:", _error.message);
1410
- }
1411
- }
1412
- }, this.healthCheckFrequency);
1413
- }
1414
- /**
1415
- * Get connection statistics
1416
- *
1417
- * @returns {Object} Connection statistics
1418
- */
1419
- getStats() {
1420
- return {
1421
- ...this.stats,
1422
- isConnected: this.isConnected,
1423
- poolStats: this.pool ? this.adapter.getPoolStats(this.pool) : null
1424
- };
1425
- }
1426
- /**
1427
- * Close database connection
1428
- *
1429
- * @returns {Promise<void>}
1430
- */
1431
- async close() {
1432
- if (!this.isConnected) {
1433
- return;
1434
- }
1435
- if (this.healthCheckInterval) {
1436
- clearInterval(this.healthCheckInterval);
1437
- this.healthCheckInterval = null;
1438
- }
1439
- if (this.pool && this.adapter) {
1440
- await this.adapter.closePool(this.pool);
1441
- }
1442
- this.isConnected = false;
1443
- this.pool = null;
1444
- this.adapter = null;
1445
- this.emit("disconnected");
1446
- if (this.config.debug) {
1447
- console.log("Database connection closed");
1448
- }
1449
- }
1450
- };
1451
- function createDatabaseManager(config) {
1452
- return new DatabaseManager(config);
1453
- }
1454
-
1455
- // src/middleware.js
1456
- function withDatabase(db, options = {}) {
1457
- const config = {
1458
- autoConnect: true,
1459
- attachModels: true,
1460
- transactionKey: "tx",
1461
- ...options
1462
- };
1463
- return async (req, res, next) => {
1464
- try {
1465
- if (config.autoConnect && !db.isConnected) {
1466
- await db.connect();
1467
- }
1468
- req.db = db;
1469
- req.dbQuery = async (sql, params, queryOptions) => {
1470
- return await db.query(sql, params, queryOptions);
1471
- };
1472
- req.transaction = async (callback) => {
1473
- const tx = await db.transaction();
1474
- try {
1475
- const result = await callback(tx);
1476
- await tx.commit();
1477
- return result;
1478
- } catch (_error) {
1479
- await tx.rollback();
1480
- throw _error;
1481
- }
1482
- };
1483
- if (config.attachModels && db.models) {
1484
- req.models = db.models;
1485
- }
1486
- await next();
1487
- } catch (_error) {
1488
- console.error("Database middleware _error:", _error);
1489
- if (typeof next === "function") {
1490
- next(_error);
1491
- } else {
1492
- throw _error;
1493
- }
1494
- }
1495
- };
1496
- }
1497
- function withTransaction(db, options = {}) {
1498
- const config = {
1499
- isolationLevel: null,
1500
- readOnly: false,
1501
- ...options
1502
- };
1503
- return async (req, res, next) => {
1504
- const tx = await db.transaction(config);
1505
- req.tx = tx;
1506
- try {
1507
- await next();
1508
- if (!tx.isCommitted && !tx.isRolledBack) {
1509
- await tx.commit();
1510
- }
1511
- } catch (_error) {
1512
- if (!tx.isRolledBack && !tx.isCommitted) {
1513
- await tx.rollback();
1514
- }
1515
- throw _error;
1516
- }
1517
- };
1518
- }
1519
- function withModel(ModelClass, paramName = "id", requestKey = null) {
1520
- let modelName = requestKey;
1521
- if (!modelName) {
1522
- if (ModelClass && ModelClass.name) {
1523
- modelName = ModelClass.name.toLowerCase();
1524
- } else if (ModelClass && ModelClass.tableName) {
1525
- modelName = ModelClass.tableName.slice(0, -1);
1526
- } else {
1527
- modelName = "model";
1528
- }
1529
- }
1530
- const key = modelName;
1531
- return async (req, res, next) => {
1532
- try {
1533
- const paramValue = req.params[paramName];
1534
- if (!paramValue) {
1535
- const _error = new Error(`Parameter '${paramName}' is required`);
1536
- _error.status = 400;
1537
- throw _error;
1538
- }
1539
- const model = await ModelClass.find(paramValue);
1540
- if (!model) {
1541
- const _error = new Error(`${ModelClass.name} not found`);
1542
- _error.status = 404;
1543
- throw _error;
1544
- }
1545
- req[key] = model;
1546
- await next();
1547
- } catch (_error) {
1548
- if (typeof next === "function") {
1549
- next(_error);
1550
- } else {
1551
- throw _error;
1552
- }
1553
- }
1554
- };
1555
- }
1556
- function withPagination(options = {}) {
1557
- const config = {
1558
- defaultLimit: 20,
1559
- maxLimit: 100,
1560
- pageParam: "page",
1561
- limitParam: "limit",
1562
- ...options
1563
- };
1564
- return async (req, res, next) => {
1565
- const page = Math.max(1, parseInt(req.query[config.pageParam]) || 1);
1566
- const limit = Math.min(
1567
- config.maxLimit,
1568
- Math.max(1, parseInt(req.query[config.limitParam]) || config.defaultLimit)
1569
- );
1570
- const offset = (page - 1) * limit;
1571
- req.pagination = {
1572
- page,
1573
- limit,
1574
- offset,
1575
- hasNext: null,
1576
- // To be set by the handler
1577
- hasPrev: page > 1,
1578
- totalPages: null,
1579
- // To be set by the handler
1580
- totalCount: null
1581
- // To be set by the handler
1582
- };
1583
- await next();
1584
- };
1585
- }
1586
1161
 
1587
1162
  // src/adapters/postgresql.js
1588
1163
  function createPostgreSQLAdapter() {
@@ -1724,468 +1299,1112 @@ function createPostgreSQLAdapter() {
1724
1299
  client.release();
1725
1300
  }
1726
1301
  }
1727
- };
1728
- return transaction;
1729
- },
1730
- /**
1731
- * Get pool statistics
1732
- */
1733
- getPoolStats(pool) {
1734
- return {
1735
- total: pool.totalCount,
1736
- available: pool.idleCount,
1737
- acquired: pool.totalCount - pool.idleCount,
1738
- waiting: pool.waitingCount
1739
- };
1740
- },
1741
- /**
1742
- * Close connection pool
1743
- */
1744
- async closePool(pool) {
1745
- await pool.end();
1302
+ };
1303
+ return transaction;
1304
+ },
1305
+ /**
1306
+ * Get pool statistics
1307
+ */
1308
+ getPoolStats(pool) {
1309
+ return {
1310
+ total: pool.totalCount,
1311
+ available: pool.idleCount,
1312
+ acquired: pool.totalCount - pool.idleCount,
1313
+ waiting: pool.waitingCount
1314
+ };
1315
+ },
1316
+ /**
1317
+ * Close connection pool
1318
+ */
1319
+ async closePool(pool) {
1320
+ await pool.end();
1321
+ }
1322
+ };
1323
+ }
1324
+
1325
+ // src/adapters/mysql.js
1326
+ function createMySQLAdapter() {
1327
+ let mysql = null;
1328
+ async function initializeMySQL() {
1329
+ if (!mysql) {
1330
+ try {
1331
+ const mysqlModule = await import("mysql2/promise");
1332
+ mysql = mysqlModule.default || mysqlModule;
1333
+ } catch {
1334
+ throw new Error("mysql2 package is required for MySQL adapter. Install with: npm install mysql2");
1335
+ }
1336
+ }
1337
+ }
1338
+ return {
1339
+ /**
1340
+ * Create connection pool
1341
+ */
1342
+ async createPool(config) {
1343
+ await initializeMySQL();
1344
+ const poolConfig = {
1345
+ host: config.host,
1346
+ port: config.port,
1347
+ database: config.database,
1348
+ user: config.username,
1349
+ password: config.password,
1350
+ connectionLimit: config.pool.max,
1351
+ acquireTimeout: config.pool.acquireTimeoutMillis,
1352
+ timeout: config.pool.createTimeoutMillis,
1353
+ reconnect: true,
1354
+ charset: "utf8mb4",
1355
+ timezone: "Z"
1356
+ };
1357
+ const pool = mysql.createPool(poolConfig);
1358
+ return pool;
1359
+ },
1360
+ /**
1361
+ * Test database connection
1362
+ */
1363
+ async testConnection(pool) {
1364
+ const connection = await pool.getConnection();
1365
+ try {
1366
+ await connection.query("SELECT 1");
1367
+ } finally {
1368
+ connection.release();
1369
+ }
1370
+ },
1371
+ /**
1372
+ * Execute database query
1373
+ */
1374
+ async query(pool, sql, params = [], options = {}) {
1375
+ const connection = await pool.getConnection();
1376
+ try {
1377
+ const [rows] = await connection.execute(sql, params);
1378
+ if (options.single) {
1379
+ return Array.isArray(rows) ? rows[0] || null : rows;
1380
+ }
1381
+ if (Array.isArray(rows)) {
1382
+ return {
1383
+ rows,
1384
+ rowCount: rows.length,
1385
+ affectedRows: rows.affectedRows || rows.length,
1386
+ insertId: rows.insertId || null
1387
+ };
1388
+ } else {
1389
+ return {
1390
+ rows: [],
1391
+ rowCount: rows.affectedRows || 0,
1392
+ affectedRows: rows.affectedRows || 0,
1393
+ insertId: rows.insertId || null
1394
+ };
1395
+ }
1396
+ } finally {
1397
+ connection.release();
1398
+ }
1399
+ },
1400
+ /**
1401
+ * Start database transaction
1402
+ */
1403
+ async transaction(pool) {
1404
+ const connection = await pool.getConnection();
1405
+ await connection.beginTransaction();
1406
+ const transaction = {
1407
+ connection,
1408
+ pool,
1409
+ isCommitted: false,
1410
+ isRolledBack: false,
1411
+ query: async (sql, params, queryOptions) => {
1412
+ if (transaction.isCommitted || transaction.isRolledBack) {
1413
+ throw new Error("Cannot execute query on completed transaction");
1414
+ }
1415
+ const [rows] = await connection.execute(sql, params);
1416
+ if (queryOptions && queryOptions.single) {
1417
+ return Array.isArray(rows) ? rows[0] || null : rows;
1418
+ }
1419
+ if (Array.isArray(rows)) {
1420
+ return {
1421
+ rows,
1422
+ rowCount: rows.length,
1423
+ affectedRows: rows.affectedRows || rows.length,
1424
+ insertId: rows.insertId || null
1425
+ };
1426
+ } else {
1427
+ return {
1428
+ rows: [],
1429
+ rowCount: rows.affectedRows || 0,
1430
+ affectedRows: rows.affectedRows || 0,
1431
+ insertId: rows.insertId || null
1432
+ };
1433
+ }
1434
+ },
1435
+ commit: async () => {
1436
+ if (transaction.isCommitted || transaction.isRolledBack) {
1437
+ throw new Error("Transaction already completed");
1438
+ }
1439
+ try {
1440
+ await connection.commit();
1441
+ transaction.isCommitted = true;
1442
+ } finally {
1443
+ connection.release();
1444
+ }
1445
+ },
1446
+ rollback: async () => {
1447
+ if (transaction.isCommitted || transaction.isRolledBack) {
1448
+ throw new Error("Transaction already completed");
1449
+ }
1450
+ try {
1451
+ await connection.rollback();
1452
+ transaction.isRolledBack = true;
1453
+ } finally {
1454
+ connection.release();
1455
+ }
1456
+ }
1457
+ };
1458
+ return transaction;
1459
+ },
1460
+ /**
1461
+ * Get pool statistics
1462
+ */
1463
+ getPoolStats(pool) {
1464
+ return {
1465
+ total: pool.config.connectionLimit,
1466
+ available: pool._freeConnections ? pool._freeConnections.length : 0,
1467
+ acquired: pool._allConnections ? pool._allConnections.length - (pool._freeConnections ? pool._freeConnections.length : 0) : 0,
1468
+ waiting: pool._connectionQueue ? pool._connectionQueue.length : 0
1469
+ };
1470
+ },
1471
+ /**
1472
+ * Close connection pool
1473
+ */
1474
+ async closePool(pool) {
1475
+ await pool.end();
1476
+ }
1477
+ };
1478
+ }
1479
+
1480
+ // src/adapters/sqlite.js
1481
+ function createSQLiteAdapter() {
1482
+ let sqlite3 = null;
1483
+ let db = null;
1484
+ async function initializeSQLite() {
1485
+ if (!sqlite3) {
1486
+ try {
1487
+ const sqlite3Module = await import("sqlite3");
1488
+ sqlite3 = sqlite3Module.default || sqlite3Module;
1489
+ } catch {
1490
+ throw new Error("Failed to load sqlite3 module. Make sure to install it: npm install sqlite3");
1491
+ }
1492
+ }
1493
+ }
1494
+ async function connect(config) {
1495
+ await initializeSQLite();
1496
+ return new Promise((resolve, reject) => {
1497
+ try {
1498
+ db = new sqlite3.Database(
1499
+ config.database,
1500
+ config.readonly ? sqlite3.OPEN_READONLY : sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE,
1501
+ (err) => {
1502
+ if (err) {
1503
+ return reject(new Error(`Failed to connect to SQLite database: ${err.message}`));
1504
+ }
1505
+ db.run("PRAGMA foreign_keys = ON");
1506
+ db.run("PRAGMA journal_mode = WAL");
1507
+ db.run("PRAGMA busy_timeout = 5000");
1508
+ resolve(instance);
1509
+ }
1510
+ );
1511
+ } catch (_error) {
1512
+ reject(new Error(`Failed to connect to SQLite database: ${_error.message}`));
1513
+ }
1514
+ });
1515
+ }
1516
+ function query(sql, params = []) {
1517
+ return new Promise((resolve, reject) => {
1518
+ if (!db) {
1519
+ return reject(new Error("Database connection not established. Call connect() first."));
1520
+ }
1521
+ db.all(sql, params, (err, rows) => {
1522
+ if (err) {
1523
+ return reject(new Error(`SQLite query _error: ${err.message}`));
1524
+ }
1525
+ resolve({ rows });
1526
+ });
1527
+ });
1528
+ }
1529
+ function execute(sql, params = []) {
1530
+ return new Promise((resolve, reject) => {
1531
+ if (!db) {
1532
+ return reject(new Error("Database connection not established. Call connect() first."));
1533
+ }
1534
+ db.run(sql, params, function(err) {
1535
+ if (err) {
1536
+ return reject(new Error(`SQLite execute _error: ${err.message}`));
1537
+ }
1538
+ resolve({
1539
+ affectedRows: this.changes,
1540
+ insertId: this.lastID
1541
+ });
1542
+ });
1543
+ });
1544
+ }
1545
+ function beginTransaction() {
1546
+ return new Promise((resolve, reject) => {
1547
+ if (!db) {
1548
+ return reject(new Error("Database connection not established. Call connect() first."));
1549
+ }
1550
+ db.run("BEGIN TRANSACTION", (err) => {
1551
+ if (err) {
1552
+ return reject(new Error(`Failed to begin transaction: ${err.message}`));
1553
+ }
1554
+ resolve();
1555
+ });
1556
+ });
1557
+ }
1558
+ function commit() {
1559
+ return new Promise((resolve, reject) => {
1560
+ if (!db) {
1561
+ return reject(new Error("Database connection not established. Call connect() first."));
1562
+ }
1563
+ db.run("COMMIT", (err) => {
1564
+ if (err) {
1565
+ return reject(new Error(`Failed to commit transaction: ${err.message}`));
1566
+ }
1567
+ resolve();
1568
+ });
1569
+ });
1570
+ }
1571
+ function rollback() {
1572
+ return new Promise((resolve, reject) => {
1573
+ if (!db) {
1574
+ return reject(new Error("Database connection not established. Call connect() first."));
1575
+ }
1576
+ db.run("ROLLBACK", (err) => {
1577
+ if (err) {
1578
+ return reject(new Error(`Failed to rollback transaction: ${err.message}`));
1579
+ }
1580
+ resolve();
1581
+ });
1582
+ });
1583
+ }
1584
+ function disconnect() {
1585
+ return new Promise((resolve, reject) => {
1586
+ if (!db) {
1587
+ return resolve();
1588
+ }
1589
+ db.close((err) => {
1590
+ if (err) {
1591
+ return reject(new Error(`Failed to close database connection: ${err.message}`));
1592
+ }
1593
+ db = null;
1594
+ resolve();
1595
+ });
1596
+ });
1597
+ }
1598
+ function getConnection() {
1599
+ if (!db) {
1600
+ throw new Error("Database connection not established. Call connect() first.");
1601
+ }
1602
+ return db;
1603
+ }
1604
+ async function ping() {
1605
+ try {
1606
+ await query("SELECT 1");
1607
+ return true;
1608
+ } catch {
1609
+ return false;
1610
+ }
1611
+ }
1612
+ function escape(value) {
1613
+ if (value === null || value === void 0) {
1614
+ return "NULL";
1615
+ }
1616
+ if (typeof value === "boolean") {
1617
+ return value ? "1" : "0";
1618
+ }
1619
+ if (typeof value === "number") {
1620
+ return String(value);
1746
1621
  }
1622
+ return `'${String(value).replace(/'/g, "''")}'`;
1623
+ }
1624
+ const instance = {
1625
+ connect,
1626
+ query,
1627
+ execute,
1628
+ beginTransaction,
1629
+ commit,
1630
+ rollback,
1631
+ disconnect,
1632
+ getConnection,
1633
+ ping,
1634
+ escape
1747
1635
  };
1636
+ return instance;
1748
1637
  }
1749
1638
 
1750
- // src/adapters/mysql.js
1751
- function createMySQLAdapter() {
1752
- let mysql = null;
1753
- async function initializeMySQL() {
1754
- if (!mysql) {
1639
+ // src/adapters/mongodb.js
1640
+ function createMongoDBAdapter() {
1641
+ let mongodb = null;
1642
+ let client = null;
1643
+ let db = null;
1644
+ async function initializeMongoDB() {
1645
+ if (!mongodb) {
1755
1646
  try {
1756
- const mysqlModule = await import("mysql2/promise");
1757
- mysql = mysqlModule.default || mysqlModule;
1647
+ const mongoModule = await import("mongodb");
1648
+ mongodb = mongoModule;
1758
1649
  } catch {
1759
- throw new Error("mysql2 package is required for MySQL adapter. Install with: npm install mysql2");
1650
+ throw new Error("Failed to load mongodb module. Make sure to install it: npm install mongodb");
1760
1651
  }
1761
1652
  }
1762
1653
  }
1763
- return {
1764
- /**
1765
- * Create connection pool
1766
- */
1767
- async createPool(config) {
1768
- await initializeMySQL();
1769
- const poolConfig = {
1770
- host: config.host,
1771
- port: config.port,
1772
- database: config.database,
1773
- user: config.username,
1774
- password: config.password,
1775
- connectionLimit: config.pool.max,
1776
- acquireTimeout: config.pool.acquireTimeoutMillis,
1777
- timeout: config.pool.createTimeoutMillis,
1778
- reconnect: true,
1779
- charset: "utf8mb4",
1780
- timezone: "Z"
1781
- };
1782
- const pool = mysql.createPool(poolConfig);
1783
- return pool;
1784
- },
1785
- /**
1786
- * Test database connection
1787
- */
1788
- async testConnection(pool) {
1789
- const connection = await pool.getConnection();
1790
- try {
1791
- await connection.query("SELECT 1");
1792
- } finally {
1793
- connection.release();
1654
+ async function connect(config) {
1655
+ await initializeMongoDB();
1656
+ try {
1657
+ client = new mongodb.MongoClient(config.url, config.options || {});
1658
+ await client.connect();
1659
+ db = client.db(config.database);
1660
+ return instance;
1661
+ } catch (_error) {
1662
+ throw new Error(`Failed to connect to MongoDB: ${_error.message}`);
1663
+ }
1664
+ }
1665
+ async function query(collectionName, query2 = {}, options = {}) {
1666
+ if (!db) {
1667
+ throw new Error("Database connection not established. Call connect() first.");
1668
+ }
1669
+ try {
1670
+ const collection = db.collection(collectionName);
1671
+ const cursor = collection.find(query2, options);
1672
+ if (options.sort) {
1673
+ cursor.sort(options.sort);
1794
1674
  }
1795
- },
1796
- /**
1797
- * Execute database query
1798
- */
1799
- async query(pool, sql, params = [], options = {}) {
1800
- const connection = await pool.getConnection();
1801
- try {
1802
- const [rows] = await connection.execute(sql, params);
1803
- if (options.single) {
1804
- return Array.isArray(rows) ? rows[0] || null : rows;
1805
- }
1806
- if (Array.isArray(rows)) {
1807
- return {
1808
- rows,
1809
- rowCount: rows.length,
1810
- affectedRows: rows.affectedRows || rows.length,
1811
- insertId: rows.insertId || null
1812
- };
1813
- } else {
1814
- return {
1815
- rows: [],
1816
- rowCount: rows.affectedRows || 0,
1817
- affectedRows: rows.affectedRows || 0,
1818
- insertId: rows.insertId || null
1819
- };
1675
+ if (options.limit) {
1676
+ cursor.limit(options.limit);
1677
+ }
1678
+ if (options.skip) {
1679
+ cursor.skip(options.skip);
1680
+ }
1681
+ if (options.projection) {
1682
+ cursor.project(options.projection);
1683
+ }
1684
+ return cursor.toArray();
1685
+ } catch (_error) {
1686
+ throw new Error(`MongoDB query _error: ${_error.message}`);
1687
+ }
1688
+ }
1689
+ async function execute(command) {
1690
+ if (!db) {
1691
+ throw new Error("Database connection not established. Call connect() first.");
1692
+ }
1693
+ try {
1694
+ return await db.command(command);
1695
+ } catch (_error) {
1696
+ throw new Error(`MongoDB command _error: ${_error.message}`);
1697
+ }
1698
+ }
1699
+ async function beginTransaction() {
1700
+ if (!client) {
1701
+ throw new Error("Database connection not established. Call connect() first.");
1702
+ }
1703
+ const session = client.startSession();
1704
+ session.startTransaction();
1705
+ return session;
1706
+ }
1707
+ async function commit(session) {
1708
+ if (!session) {
1709
+ throw new Error("No active transaction session");
1710
+ }
1711
+ try {
1712
+ await session.commitTransaction();
1713
+ } finally {
1714
+ await session.endSession();
1715
+ }
1716
+ }
1717
+ async function rollback(session) {
1718
+ if (!session) {
1719
+ throw new Error("No active transaction session");
1720
+ }
1721
+ try {
1722
+ await session.abortTransaction();
1723
+ } finally {
1724
+ await session.endSession();
1725
+ }
1726
+ }
1727
+ async function disconnect() {
1728
+ if (client) {
1729
+ await client.close();
1730
+ client = null;
1731
+ db = null;
1732
+ }
1733
+ }
1734
+ function getConnection() {
1735
+ if (!db) {
1736
+ throw new Error("Database connection not established. Call connect() first.");
1737
+ }
1738
+ return db;
1739
+ }
1740
+ async function ping() {
1741
+ try {
1742
+ await db.command({ ping: 1 });
1743
+ return true;
1744
+ } catch {
1745
+ return false;
1746
+ }
1747
+ }
1748
+ function escape(value) {
1749
+ return value;
1750
+ }
1751
+ const instance = {
1752
+ connect,
1753
+ query,
1754
+ execute,
1755
+ beginTransaction,
1756
+ commit,
1757
+ rollback,
1758
+ disconnect,
1759
+ getConnection,
1760
+ ping,
1761
+ escape
1762
+ };
1763
+ return instance;
1764
+ }
1765
+
1766
+ // src/adapters/memory.js
1767
+ var MemoryAdapter = class {
1768
+ constructor() {
1769
+ this.stores = /* @__PURE__ */ new Map();
1770
+ }
1771
+ /**
1772
+ * Create a new in-memory store
1773
+ *
1774
+ * @param {Object} config - Store configuration
1775
+ * @returns {Promise<Object>} Store instance
1776
+ */
1777
+ async createPool(config) {
1778
+ const collections = /* @__PURE__ */ new Map();
1779
+ const schemas = /* @__PURE__ */ new Map();
1780
+ const store = {
1781
+ config,
1782
+ stats: {
1783
+ created: Date.now(),
1784
+ operations: 0,
1785
+ collections: 0,
1786
+ queries: 0
1787
+ },
1788
+ /**
1789
+ * Get a collection by name
1790
+ * @private
1791
+ */
1792
+ _getCollection(name) {
1793
+ if (!collections.has(name)) {
1794
+ collections.set(name, /* @__PURE__ */ new Map());
1820
1795
  }
1821
- } finally {
1822
- connection.release();
1823
- }
1824
- },
1825
- /**
1826
- * Start database transaction
1827
- */
1828
- async transaction(pool) {
1829
- const connection = await pool.getConnection();
1830
- await connection.beginTransaction();
1831
- const transaction = {
1832
- connection,
1833
- pool,
1834
- isCommitted: false,
1835
- isRolledBack: false,
1836
- query: async (sql, params, queryOptions) => {
1837
- if (transaction.isCommitted || transaction.isRolledBack) {
1838
- throw new Error("Cannot execute query on completed transaction");
1796
+ return collections.get(name);
1797
+ },
1798
+ /**
1799
+ * Get schema for a collection
1800
+ * @private
1801
+ */
1802
+ _getSchema(collectionName) {
1803
+ return schemas.get(collectionName) || {};
1804
+ },
1805
+ /**
1806
+ * Execute a query
1807
+ * @param {string} operation - Operation type
1808
+ * @param {Object} params - Query parameters
1809
+ * @returns {Promise<*>} Query result
1810
+ */
1811
+ async query(operation, params = {}) {
1812
+ this.stats.operations++;
1813
+ this.stats.queries++;
1814
+ const { table, where = {}, data, limit, offset, orderBy } = params;
1815
+ const collection = this._getCollection(table);
1816
+ switch (operation.toUpperCase()) {
1817
+ case "FIND": {
1818
+ let results = Array.from(collection.values());
1819
+ if (Object.keys(where).length > 0) {
1820
+ results = results.filter(
1821
+ (item) => Object.entries(where).every(([key, value]) => {
1822
+ if (value === void 0) return true;
1823
+ if (value === null) return item[key] === null;
1824
+ return JSON.stringify(item[key]) === JSON.stringify(value);
1825
+ })
1826
+ );
1827
+ }
1828
+ if (orderBy) {
1829
+ let field, direction;
1830
+ if (Array.isArray(orderBy)) {
1831
+ [field, direction = "ASC"] = orderBy;
1832
+ } else if (typeof orderBy === "object") {
1833
+ const entries = Object.entries(orderBy);
1834
+ if (entries.length > 0) {
1835
+ [field, direction] = entries[0];
1836
+ }
1837
+ } else {
1838
+ field = orderBy;
1839
+ direction = "ASC";
1840
+ }
1841
+ if (field) {
1842
+ const dir = direction.toLowerCase();
1843
+ results.sort((a, b) => {
1844
+ if (a[field] < b[field]) return dir === "asc" ? -1 : 1;
1845
+ if (a[field] > b[field]) return dir === "asc" ? 1 : -1;
1846
+ return 0;
1847
+ });
1848
+ }
1849
+ }
1850
+ if (offset) results = results.slice(offset);
1851
+ if (limit) results = results.slice(0, limit);
1852
+ return results;
1839
1853
  }
1840
- const [rows] = await connection.execute(sql, params);
1841
- if (queryOptions && queryOptions.single) {
1842
- return Array.isArray(rows) ? rows[0] || null : rows;
1854
+ case "INSERT": {
1855
+ if (!data) throw new Error("No data provided for insert");
1856
+ const id = data.id || Date.now().toString(36) + Math.random().toString(36).substr(2);
1857
+ const record = { ...data, id };
1858
+ collection.set(id, record);
1859
+ this.stats.collections = collections.size;
1860
+ return { id };
1843
1861
  }
1844
- if (Array.isArray(rows)) {
1845
- return {
1846
- rows,
1847
- rowCount: rows.length,
1848
- affectedRows: rows.affectedRows || rows.length,
1849
- insertId: rows.insertId || null
1850
- };
1851
- } else {
1852
- return {
1853
- rows: [],
1854
- rowCount: rows.affectedRows || 0,
1855
- affectedRows: rows.affectedRows || 0,
1856
- insertId: rows.insertId || null
1857
- };
1862
+ case "UPDATE": {
1863
+ if (!data) throw new Error("No data provided for update");
1864
+ const records = await this.query("FIND", { table, where });
1865
+ const updated = [];
1866
+ for (const record of records) {
1867
+ const updatedRecord = { ...record, ...data };
1868
+ collection.set(record.id, updatedRecord);
1869
+ updated.push(updatedRecord);
1870
+ }
1871
+ return { affectedRows: updated.length };
1858
1872
  }
1859
- },
1860
- commit: async () => {
1861
- if (transaction.isCommitted || transaction.isRolledBack) {
1862
- throw new Error("Transaction already completed");
1873
+ case "DELETE": {
1874
+ const records = await this.query("FIND", { table, where });
1875
+ const deleted = [];
1876
+ for (const record of records) {
1877
+ if (collection.delete(record.id)) {
1878
+ deleted.push(record);
1879
+ }
1880
+ }
1881
+ return { affectedRows: deleted.length };
1863
1882
  }
1864
- try {
1865
- await connection.commit();
1866
- transaction.isCommitted = true;
1867
- } finally {
1868
- connection.release();
1883
+ case "COUNT": {
1884
+ const results = await this.query("FIND", { table, where });
1885
+ return { count: results.length };
1869
1886
  }
1870
- },
1871
- rollback: async () => {
1872
- if (transaction.isCommitted || transaction.isRolledBack) {
1873
- throw new Error("Transaction already completed");
1887
+ case "CREATE_COLLECTION": {
1888
+ const { name, schema } = params;
1889
+ schemas.set(name, schema || {});
1890
+ return { success: true };
1874
1891
  }
1875
- try {
1876
- await connection.rollback();
1877
- transaction.isRolledBack = true;
1878
- } finally {
1879
- connection.release();
1892
+ case "SET_SCHEMA": {
1893
+ const { model, schema } = params;
1894
+ schemas.set(model, schema);
1895
+ return { success: true };
1880
1896
  }
1897
+ default:
1898
+ throw new Error(`Unsupported operation: ${operation}`);
1881
1899
  }
1882
- };
1883
- return transaction;
1884
- },
1885
- /**
1886
- * Get pool statistics
1887
- */
1888
- getPoolStats(pool) {
1889
- return {
1890
- total: pool.config.connectionLimit,
1891
- available: pool._freeConnections ? pool._freeConnections.length : 0,
1892
- acquired: pool._allConnections ? pool._allConnections.length - (pool._freeConnections ? pool._freeConnections.length : 0) : 0,
1893
- waiting: pool._connectionQueue ? pool._connectionQueue.length : 0
1894
- };
1895
- },
1896
- /**
1897
- * Close connection pool
1898
- */
1899
- async closePool(pool) {
1900
- await pool.end();
1900
+ },
1901
+ /**
1902
+ * Get store statistics
1903
+ * @returns {Object}
1904
+ */
1905
+ getStats() {
1906
+ return {
1907
+ ...this.stats,
1908
+ uptime: Date.now() - this.stats.created,
1909
+ collections: collections.size,
1910
+ operations: this.stats.operations,
1911
+ queries: this.stats.queries
1912
+ };
1913
+ },
1914
+ /**
1915
+ * Execute a transaction
1916
+ * @param {Function} callback - Transaction callback
1917
+ * @returns {Promise<*>} Result of the transaction
1918
+ */
1919
+ async transaction(callback) {
1920
+ try {
1921
+ const result = await callback({
1922
+ query: (operation, params) => this.query(operation, params)
1923
+ });
1924
+ return result;
1925
+ } catch (_error) {
1926
+ throw _error;
1927
+ }
1928
+ }
1929
+ };
1930
+ this.stores.set(config.name || "default", store);
1931
+ return store;
1932
+ }
1933
+ /**
1934
+ * Get a store by name
1935
+ *
1936
+ * @param {string} name - Store name
1937
+ * @returns {Object|undefined} Store instance or undefined if not found
1938
+ */
1939
+ getStore(name = "default") {
1940
+ return this.stores.get(name);
1941
+ }
1942
+ /**
1943
+ * Close all stores and clean up
1944
+ *
1945
+ * @returns {Promise<void>}
1946
+ */
1947
+ async close() {
1948
+ this.stores.clear();
1949
+ }
1950
+ /**
1951
+ * Close the connection pool
1952
+ * @param {Object} pool - The connection pool to close
1953
+ * @returns {Promise<void>}
1954
+ */
1955
+ async closePool(pool) {
1956
+ if (pool) {
1957
+ if (pool.collections) {
1958
+ pool.collections.clear();
1959
+ }
1960
+ if (pool.schemas) {
1961
+ pool.schemas.clear();
1962
+ }
1901
1963
  }
1902
- };
1903
- }
1964
+ return Promise.resolve();
1965
+ }
1966
+ /**
1967
+ * Execute a transaction
1968
+ * @param {Function} callback - Transaction callback
1969
+ * @returns {Promise<*>} Result of the transaction
1970
+ */
1971
+ async transaction(callback) {
1972
+ try {
1973
+ const result = await callback({
1974
+ query: (operation, params) => this.query(operation, params)
1975
+ });
1976
+ return result;
1977
+ } catch (_error) {
1978
+ throw _error;
1979
+ }
1980
+ }
1981
+ };
1904
1982
 
1905
- // src/adapters/sqlite.js
1906
- function createSQLiteAdapter() {
1907
- let sqlite3 = null;
1908
- let db = null;
1909
- async function initializeSQLite() {
1910
- if (!sqlite3) {
1911
- try {
1912
- const sqlite3Module = await import("sqlite3");
1913
- sqlite3 = sqlite3Module.default || sqlite3Module;
1914
- } catch {
1915
- throw new Error("Failed to load sqlite3 module. Make sure to install it: npm install sqlite3");
1983
+ // src/connection-manager.js
1984
+ var DatabaseManager = class extends EventEmitter {
1985
+ constructor(config) {
1986
+ super();
1987
+ this.config = this.validateConfig(config);
1988
+ this.adapter = null;
1989
+ this.pool = null;
1990
+ this.isConnected = false;
1991
+ this.connectionAttempts = 0;
1992
+ this.maxRetries = 3;
1993
+ this.healthCheckInterval = null;
1994
+ this.healthCheckFrequency = 3e4;
1995
+ this.stats = {
1996
+ totalConnections: 0,
1997
+ activeConnections: 0,
1998
+ failedConnections: 0,
1999
+ queriesExecuted: 0,
2000
+ averageQueryTime: 0,
2001
+ lastHealthCheck: null
2002
+ };
2003
+ }
2004
+ /**
2005
+ * Validate database configuration
2006
+ *
2007
+ * @private
2008
+ * @param {Object} config - Configuration to validate
2009
+ * @returns {Object} Validated configuration
2010
+ * @throws {Error} If configuration is invalid
2011
+ */
2012
+ validateConfig(config) {
2013
+ if (!config || typeof config !== "object") {
2014
+ throw new Error("Database configuration is required");
2015
+ }
2016
+ if (config.adapter) {
2017
+ if (typeof config.adapter !== "object" || typeof config.adapter.createPool !== "function") {
2018
+ throw new Error("Invalid adapter provided. Adapter must be an object with a createPool method");
2019
+ }
2020
+ if (!config.store) {
2021
+ config.store = { name: "default" };
2022
+ } else if (typeof config.store === "string") {
2023
+ config.store = { name: config.store };
1916
2024
  }
2025
+ return config;
1917
2026
  }
1918
- }
1919
- async function connect(config) {
1920
- await initializeSQLite();
1921
- return new Promise((resolve, reject) => {
1922
- try {
1923
- db = new sqlite3.Database(
1924
- config.database,
1925
- config.readonly ? sqlite3.OPEN_READONLY : sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE,
1926
- (err) => {
1927
- if (err) {
1928
- return reject(new Error(`Failed to connect to SQLite database: ${err.message}`));
1929
- }
1930
- db.run("PRAGMA foreign_keys = ON");
1931
- db.run("PRAGMA journal_mode = WAL");
1932
- db.run("PRAGMA busy_timeout = 5000");
1933
- resolve(instance);
1934
- }
1935
- );
1936
- } catch (_error) {
1937
- reject(new Error(`Failed to connect to SQLite database: ${_error.message}`));
2027
+ const { type, database } = config;
2028
+ if (!type) {
2029
+ throw new Error("Either database type or adapter is required");
2030
+ }
2031
+ const supportedTypes = ["postgresql", "mysql", "sqlite", "mongodb"];
2032
+ if (!supportedTypes.includes(type)) {
2033
+ throw new Error(`Unsupported database type: ${type}. Supported types: ${supportedTypes.join(", ")}`);
2034
+ }
2035
+ if (!database) {
2036
+ throw new Error("Database name is required for type-based configuration");
2037
+ }
2038
+ const defaultPorts = {
2039
+ postgresql: 5432,
2040
+ mysql: 3306,
2041
+ mongodb: 27017,
2042
+ sqlite: null
2043
+ };
2044
+ return {
2045
+ host: config.host || "localhost",
2046
+ port: config.port || defaultPorts[type],
2047
+ ...config,
2048
+ pool: {
2049
+ min: 2,
2050
+ max: 10,
2051
+ acquireTimeoutMillis: 3e4,
2052
+ createTimeoutMillis: 3e4,
2053
+ destroyTimeoutMillis: 5e3,
2054
+ idleTimeoutMillis: 3e4,
2055
+ reapIntervalMillis: 1e3,
2056
+ createRetryIntervalMillis: 200,
2057
+ ...config.pool
1938
2058
  }
1939
- });
2059
+ };
1940
2060
  }
1941
- function query(sql, params = []) {
1942
- return new Promise((resolve, reject) => {
1943
- if (!db) {
1944
- return reject(new Error("Database connection not established. Call connect() first."));
2061
+ /**
2062
+ * Connect to the database
2063
+ *
2064
+ * @returns {Promise<void>}
2065
+ * @throws {Error} If connection fails after retries
2066
+ *
2067
+ * @example
2068
+ * await db.connect();
2069
+ * console.log('Database connected successfully');
2070
+ */
2071
+ async connect() {
2072
+ if (this.isConnected) {
2073
+ return;
2074
+ }
2075
+ try {
2076
+ this.adapter = await this.loadAdapter(this.config.type);
2077
+ if (typeof this.adapter.createPool === "function") {
2078
+ this.pool = await this.adapter.createPool(this.config);
2079
+ } else if (typeof this.adapter.connect === "function") {
2080
+ this.pool = await this.adapter.connect(this.config);
2081
+ } else {
2082
+ throw new Error("Adapter must have either createPool or connect method");
1945
2083
  }
1946
- db.all(sql, params, (err, rows) => {
1947
- if (err) {
1948
- return reject(new Error(`SQLite query _error: ${err.message}`));
1949
- }
1950
- resolve({ rows });
1951
- });
1952
- });
1953
- }
1954
- function execute(sql, params = []) {
1955
- return new Promise((resolve, reject) => {
1956
- if (!db) {
1957
- return reject(new Error("Database connection not established. Call connect() first."));
2084
+ await this.testConnection();
2085
+ this.isConnected = true;
2086
+ this.connectionAttempts = 0;
2087
+ if (this.adapter.startHealthChecks) {
2088
+ this.startHealthChecks();
1958
2089
  }
1959
- db.run(sql, params, function(err) {
1960
- if (err) {
1961
- return reject(new Error(`SQLite execute _error: ${err.message}`));
1962
- }
1963
- resolve({
1964
- affectedRows: this.changes,
1965
- insertId: this.lastID
1966
- });
1967
- });
1968
- });
1969
- }
1970
- function beginTransaction() {
1971
- return new Promise((resolve, reject) => {
1972
- if (!db) {
1973
- return reject(new Error("Database connection not established. Call connect() first."));
2090
+ return this;
2091
+ } catch (_error) {
2092
+ this.connectionAttempts++;
2093
+ this.stats.failedConnections++;
2094
+ this.emit("_error", _error);
2095
+ if (this.connectionAttempts < this.maxRetries) {
2096
+ console.warn(`Connection attempt ${this.connectionAttempts} failed. Retrying in 2 seconds...`);
2097
+ await new Promise((resolve) => setTimeout(resolve, 2e3));
2098
+ return this.connect();
1974
2099
  }
1975
- db.run("BEGIN TRANSACTION", (err) => {
1976
- if (err) {
1977
- return reject(new Error(`Failed to begin transaction: ${err.message}`));
1978
- }
1979
- resolve();
1980
- });
1981
- });
2100
+ throw new Error(`Failed to connect to database after ${this.connectionAttempts} attempts: ${_error.message}`);
2101
+ }
1982
2102
  }
1983
- function commit() {
1984
- return new Promise((resolve, reject) => {
1985
- if (!db) {
1986
- return reject(new Error("Database connection not established. Call connect() first."));
1987
- }
1988
- db.run("COMMIT", (err) => {
1989
- if (err) {
1990
- return reject(new Error(`Failed to commit transaction: ${err.message}`));
1991
- }
1992
- resolve();
1993
- });
1994
- });
2103
+ /**
2104
+ * Load database adapter
2105
+ *
2106
+ * @private
2107
+ * @param {string} type - Database type
2108
+ * @returns {Object} Database adapter
2109
+ */
2110
+ loadAdapter(type) {
2111
+ if (this.config.adapter) {
2112
+ return Promise.resolve(this.config.adapter);
2113
+ }
2114
+ const adapterFactories = {
2115
+ postgresql: createPostgreSQLAdapter,
2116
+ mysql: createMySQLAdapter,
2117
+ sqlite: createSQLiteAdapter,
2118
+ mongodb: createMongoDBAdapter,
2119
+ memory: () => new MemoryAdapter()
2120
+ };
2121
+ const adapterFactory = adapterFactories[type];
2122
+ if (!adapterFactory) {
2123
+ return Promise.reject(new Error(`No adapter found for database type: ${type}`));
2124
+ }
2125
+ try {
2126
+ const adapter = adapterFactory();
2127
+ return Promise.resolve(adapter);
2128
+ } catch (_error) {
2129
+ return Promise.reject(new Error(`Failed to load ${type} adapter: ${_error.message}`));
2130
+ }
1995
2131
  }
1996
- function rollback() {
1997
- return new Promise((resolve, reject) => {
1998
- if (!db) {
1999
- return reject(new Error("Database connection not established. Call connect() first."));
2132
+ /**
2133
+ * Test database connection
2134
+ *
2135
+ * @private
2136
+ * @returns {Promise<void>}
2137
+ */
2138
+ async testConnection() {
2139
+ const startTime = Date.now();
2140
+ try {
2141
+ if (typeof this.adapter.testConnection === "function") {
2142
+ await this.adapter.testConnection(this.pool);
2143
+ } else if (this.adapter.ping) {
2144
+ await this.adapter.ping();
2000
2145
  }
2001
- db.run("ROLLBACK", (err) => {
2002
- if (err) {
2003
- return reject(new Error(`Failed to rollback transaction: ${err.message}`));
2004
- }
2005
- resolve();
2006
- });
2007
- });
2146
+ const duration = Date.now() - startTime;
2147
+ this.stats.lastHealthCheck = /* @__PURE__ */ new Date();
2148
+ this.emit("connect:test", { duration });
2149
+ } catch (_error) {
2150
+ this.emit("_error", _error);
2151
+ throw new Error(`Database connection test failed: ${_error.message}`);
2152
+ }
2008
2153
  }
2009
- function disconnect() {
2010
- return new Promise((resolve, reject) => {
2011
- if (!db) {
2012
- return resolve();
2154
+ /**
2155
+ * Execute a database query
2156
+ *
2157
+ * @param {string} sql - SQL query string
2158
+ * @param {Array} [params=[]] - Query parameters
2159
+ * @param {Object} [options={}] - Query options
2160
+ * @returns {Promise<Object>} Query result
2161
+ *
2162
+ * @example
2163
+ * const users = await db.query('SELECT * FROM users WHERE age > ?', [18]);
2164
+ * const user = await db.query('SELECT * FROM users WHERE id = ?', [123], { single: true });
2165
+ */
2166
+ async query(operation, params = {}) {
2167
+ if (!this.isConnected) {
2168
+ throw new Error("Database not connected. Call connect() first.");
2169
+ }
2170
+ const startTime = Date.now();
2171
+ try {
2172
+ let result;
2173
+ if (typeof this.pool.query === "function") {
2174
+ result = await this.pool.query(operation, params);
2175
+ } else if (typeof this.adapter.query === "function") {
2176
+ result = await this.adapter.query(this.pool, operation, params);
2177
+ } else {
2178
+ throw new Error("No valid query method found on adapter or pool");
2013
2179
  }
2014
- db.close((err) => {
2015
- if (err) {
2016
- return reject(new Error(`Failed to close database connection: ${err.message}`));
2017
- }
2018
- db = null;
2019
- resolve();
2020
- });
2021
- });
2180
+ const duration = Date.now() - startTime;
2181
+ this.stats.queriesExecuted++;
2182
+ this.stats.averageQueryTime = (this.stats.averageQueryTime * (this.stats.queriesExecuted - 1) + duration) / this.stats.queriesExecuted;
2183
+ if (this.config.debug) {
2184
+ console.log(`Query executed in ${duration}ms: ${operation}`, params);
2185
+ }
2186
+ this.emit("query", { operation, params, duration });
2187
+ return result;
2188
+ } catch (_error) {
2189
+ const duration = Date.now() - startTime;
2190
+ this.emit("queryError", { operation, params, duration, _error: _error.message });
2191
+ throw new Error(`Query failed: ${_error.message}`);
2192
+ }
2022
2193
  }
2023
- function getConnection() {
2024
- if (!db) {
2025
- throw new Error("Database connection not established. Call connect() first.");
2194
+ /**
2195
+ * Start a database transaction
2196
+ *
2197
+ * @returns {Promise<Object>} Transaction object
2198
+ *
2199
+ * @example
2200
+ * const tx = await db.transaction();
2201
+ * try {
2202
+ * await tx.query('INSERT INTO users (name) VALUES (?)', ['John']);
2203
+ * await tx.query('INSERT INTO profiles (user_id) VALUES (?)', [userId]);
2204
+ * await tx.commit();
2205
+ * } catch (_error) {
2206
+ * await tx.rollback();
2207
+ * throw _error;
2208
+ * }
2209
+ */
2210
+ async transaction() {
2211
+ if (!this.isConnected) {
2212
+ throw new Error("Database not connected. Call connect() first.");
2026
2213
  }
2027
- return db;
2214
+ return await this.adapter.transaction(this.pool);
2028
2215
  }
2029
- async function ping() {
2030
- try {
2031
- await query("SELECT 1");
2032
- return true;
2033
- } catch {
2034
- return false;
2216
+ /**
2217
+ * Start health check monitoring
2218
+ *
2219
+ * @private
2220
+ */
2221
+ startHealthCheck() {
2222
+ if (this.healthCheckInterval) {
2223
+ return;
2035
2224
  }
2225
+ this.healthCheckInterval = setInterval(async () => {
2226
+ try {
2227
+ await this.testConnection();
2228
+ this.emit("healthCheck", { status: "healthy", timestamp: /* @__PURE__ */ new Date() });
2229
+ } catch (_error) {
2230
+ this.emit("healthCheck", { status: "unhealthy", _error: _error.message, timestamp: /* @__PURE__ */ new Date() });
2231
+ if (this.config.debug) {
2232
+ console.error("Database health check failed:", _error.message);
2233
+ }
2234
+ }
2235
+ }, this.healthCheckFrequency);
2036
2236
  }
2037
- function escape(value) {
2038
- if (value === null || value === void 0) {
2039
- return "NULL";
2237
+ /**
2238
+ * Get connection statistics
2239
+ *
2240
+ * @returns {Object} Connection statistics
2241
+ */
2242
+ getStats() {
2243
+ return {
2244
+ ...this.stats,
2245
+ isConnected: this.isConnected,
2246
+ poolStats: this.pool ? this.adapter.getPoolStats(this.pool) : null
2247
+ };
2248
+ }
2249
+ /**
2250
+ * Close database connection
2251
+ *
2252
+ * @returns {Promise<void>}
2253
+ */
2254
+ async close() {
2255
+ if (!this.isConnected) {
2256
+ return;
2040
2257
  }
2041
- if (typeof value === "boolean") {
2042
- return value ? "1" : "0";
2258
+ if (this.healthCheckInterval) {
2259
+ clearInterval(this.healthCheckInterval);
2260
+ this.healthCheckInterval = null;
2261
+ }
2262
+ if (this.pool && this.adapter) {
2263
+ await this.adapter.closePool(this.pool);
2043
2264
  }
2044
- if (typeof value === "number") {
2045
- return String(value);
2265
+ this.isConnected = false;
2266
+ this.pool = null;
2267
+ this.adapter = null;
2268
+ this.emit("disconnected");
2269
+ if (this.config.debug) {
2270
+ console.log("Database connection closed");
2046
2271
  }
2047
- return `'${String(value).replace(/'/g, "''")}'`;
2048
2272
  }
2049
- const instance = {
2050
- connect,
2051
- query,
2052
- execute,
2053
- beginTransaction,
2054
- commit,
2055
- rollback,
2056
- disconnect,
2057
- getConnection,
2058
- ping,
2059
- escape
2060
- };
2061
- return instance;
2273
+ };
2274
+ function createDatabaseManager(config) {
2275
+ return new DatabaseManager(config);
2062
2276
  }
2063
2277
 
2064
- // src/adapters/mongodb.js
2065
- function createMongoDBAdapter() {
2066
- let mongodb = null;
2067
- let client = null;
2068
- let db = null;
2069
- async function initializeMongoDB() {
2070
- if (!mongodb) {
2071
- try {
2072
- const mongoModule = await import("mongodb");
2073
- mongodb = mongoModule;
2074
- } catch {
2075
- throw new Error("Failed to load mongodb module. Make sure to install it: npm install mongodb");
2076
- }
2077
- }
2078
- }
2079
- async function connect(config) {
2080
- await initializeMongoDB();
2081
- try {
2082
- client = new mongodb.MongoClient(config.url, config.options || {});
2083
- await client.connect();
2084
- db = client.db(config.database);
2085
- return instance;
2086
- } catch (_error) {
2087
- throw new Error(`Failed to connect to MongoDB: ${_error.message}`);
2088
- }
2089
- }
2090
- async function query(collectionName, query2 = {}, options = {}) {
2091
- if (!db) {
2092
- throw new Error("Database connection not established. Call connect() first.");
2093
- }
2278
+ // src/middleware.js
2279
+ function withDatabase(db, options = {}) {
2280
+ const config = {
2281
+ autoConnect: true,
2282
+ attachModels: true,
2283
+ transactionKey: "tx",
2284
+ ...options
2285
+ };
2286
+ return async (req, res, next) => {
2094
2287
  try {
2095
- const collection = db.collection(collectionName);
2096
- const cursor = collection.find(query2, options);
2097
- if (options.sort) {
2098
- cursor.sort(options.sort);
2099
- }
2100
- if (options.limit) {
2101
- cursor.limit(options.limit);
2102
- }
2103
- if (options.skip) {
2104
- cursor.skip(options.skip);
2288
+ if (config.autoConnect && !db.isConnected) {
2289
+ await db.connect();
2105
2290
  }
2106
- if (options.projection) {
2107
- cursor.project(options.projection);
2291
+ req.db = db;
2292
+ req.dbQuery = async (sql, params, queryOptions) => {
2293
+ return await db.query(sql, params, queryOptions);
2294
+ };
2295
+ req.transaction = async (callback) => {
2296
+ const tx = await db.transaction();
2297
+ try {
2298
+ const result = await callback(tx);
2299
+ await tx.commit();
2300
+ return result;
2301
+ } catch (_error) {
2302
+ await tx.rollback();
2303
+ throw _error;
2304
+ }
2305
+ };
2306
+ if (config.attachModels && db.models) {
2307
+ req.models = db.models;
2108
2308
  }
2109
- return cursor.toArray();
2309
+ await next();
2110
2310
  } catch (_error) {
2111
- throw new Error(`MongoDB query _error: ${_error.message}`);
2112
- }
2113
- }
2114
- async function execute(command) {
2115
- if (!db) {
2116
- throw new Error("Database connection not established. Call connect() first.");
2311
+ console.error("Database middleware _error:", _error);
2312
+ if (typeof next === "function") {
2313
+ next(_error);
2314
+ } else {
2315
+ throw _error;
2316
+ }
2117
2317
  }
2318
+ };
2319
+ }
2320
+ function withTransaction(db, options = {}) {
2321
+ const config = {
2322
+ isolationLevel: null,
2323
+ readOnly: false,
2324
+ ...options
2325
+ };
2326
+ return async (req, res, next) => {
2327
+ const tx = await db.transaction(config);
2328
+ req.tx = tx;
2118
2329
  try {
2119
- return await db.command(command);
2330
+ await next();
2331
+ if (!tx.isCommitted && !tx.isRolledBack) {
2332
+ await tx.commit();
2333
+ }
2120
2334
  } catch (_error) {
2121
- throw new Error(`MongoDB command _error: ${_error.message}`);
2122
- }
2123
- }
2124
- async function beginTransaction() {
2125
- if (!client) {
2126
- throw new Error("Database connection not established. Call connect() first.");
2127
- }
2128
- const session = client.startSession();
2129
- session.startTransaction();
2130
- return session;
2131
- }
2132
- async function commit(session) {
2133
- if (!session) {
2134
- throw new Error("No active transaction session");
2135
- }
2136
- try {
2137
- await session.commitTransaction();
2138
- } finally {
2139
- await session.endSession();
2140
- }
2141
- }
2142
- async function rollback(session) {
2143
- if (!session) {
2144
- throw new Error("No active transaction session");
2145
- }
2146
- try {
2147
- await session.abortTransaction();
2148
- } finally {
2149
- await session.endSession();
2150
- }
2151
- }
2152
- async function disconnect() {
2153
- if (client) {
2154
- await client.close();
2155
- client = null;
2156
- db = null;
2335
+ if (!tx.isRolledBack && !tx.isCommitted) {
2336
+ await tx.rollback();
2337
+ }
2338
+ throw _error;
2157
2339
  }
2158
- }
2159
- function getConnection() {
2160
- if (!db) {
2161
- throw new Error("Database connection not established. Call connect() first.");
2340
+ };
2341
+ }
2342
+ function withModel(ModelClass, paramName = "id", requestKey = null) {
2343
+ let modelName = requestKey;
2344
+ if (!modelName) {
2345
+ if (ModelClass && ModelClass.name) {
2346
+ modelName = ModelClass.name.toLowerCase();
2347
+ } else if (ModelClass && ModelClass.tableName) {
2348
+ modelName = ModelClass.tableName.slice(0, -1);
2349
+ } else {
2350
+ modelName = "model";
2162
2351
  }
2163
- return db;
2164
2352
  }
2165
- async function ping() {
2353
+ const key = modelName;
2354
+ return async (req, res, next) => {
2166
2355
  try {
2167
- await db.command({ ping: 1 });
2168
- return true;
2169
- } catch {
2170
- return false;
2356
+ const paramValue = req.params[paramName];
2357
+ if (!paramValue) {
2358
+ const _error = new Error(`Parameter '${paramName}' is required`);
2359
+ _error.status = 400;
2360
+ throw _error;
2361
+ }
2362
+ const model = await ModelClass.find(paramValue);
2363
+ if (!model) {
2364
+ const _error = new Error(`${ModelClass.name} not found`);
2365
+ _error.status = 404;
2366
+ throw _error;
2367
+ }
2368
+ req[key] = model;
2369
+ await next();
2370
+ } catch (_error) {
2371
+ if (typeof next === "function") {
2372
+ next(_error);
2373
+ } else {
2374
+ throw _error;
2375
+ }
2171
2376
  }
2172
- }
2173
- function escape(value) {
2174
- return value;
2175
- }
2176
- const instance = {
2177
- connect,
2178
- query,
2179
- execute,
2180
- beginTransaction,
2181
- commit,
2182
- rollback,
2183
- disconnect,
2184
- getConnection,
2185
- ping,
2186
- escape
2187
2377
  };
2188
- return instance;
2378
+ }
2379
+ function withPagination(options = {}) {
2380
+ const config = {
2381
+ defaultLimit: 20,
2382
+ maxLimit: 100,
2383
+ pageParam: "page",
2384
+ limitParam: "limit",
2385
+ ...options
2386
+ };
2387
+ return async (req, res, next) => {
2388
+ const page = Math.max(1, parseInt(req.query[config.pageParam]) || 1);
2389
+ const limit = Math.min(
2390
+ config.maxLimit,
2391
+ Math.max(1, parseInt(req.query[config.limitParam]) || config.defaultLimit)
2392
+ );
2393
+ const offset = (page - 1) * limit;
2394
+ req.pagination = {
2395
+ page,
2396
+ limit,
2397
+ offset,
2398
+ hasNext: null,
2399
+ // To be set by the handler
2400
+ hasPrev: page > 1,
2401
+ totalPages: null,
2402
+ // To be set by the handler
2403
+ totalCount: null
2404
+ // To be set by the handler
2405
+ };
2406
+ await next();
2407
+ };
2189
2408
  }
2190
2409
 
2191
2410
  // src/utils.js