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