axiodb
Version:
The Pure JavaScript Alternative to SQLite. Embedded NoSQL database for Node.js with MongoDB-style queries, zero native dependencies, built-in InMemoryCache, and web GUI. Perfect for desktop apps, CLI tools, and embedded systems. No compilation, no platfor
232 lines • 9.14 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
/* eslint-disable @typescript-eslint/no-explicit-any */
const outers_1 = require("outers");
const response_helper_1 = __importDefault(require("../../Helper/response.helper"));
const Transaction_service_1 = __importDefault(require("./Transaction.service"));
/**
* Session provides MongoDB-like session management for transactions.
* Supports automatic retry, timeout handling, and the withTransaction pattern.
*/
class Session {
constructor(collectionPath, isEncrypted = false, encryptionKey, options = {}) {
var _a, _b, _c;
this.isActive = true;
this.currentTransaction = null;
this.sessionId = new outers_1.ClassBased.UniqueGenerator(20).RandomWord(true);
this.collectionPath = collectionPath;
this.isEncrypted = isEncrypted;
this.encryptionKey = encryptionKey;
this.startTime = Date.now();
this.ResponseHelper = new response_helper_1.default();
// Default options
this.options = {
defaultTimeout: (_a = options.defaultTimeout) !== null && _a !== void 0 ? _a : 60000,
retryWrites: (_b = options.retryWrites) !== null && _b !== void 0 ? _b : true,
maxRetries: (_c = options.maxRetries) !== null && _c !== void 0 ? _c : 3,
};
}
/**
* Gets the session ID
*/
getSessionId() {
return this.sessionId;
}
/**
* Checks if the session is still active
*/
isSessionActive() {
if (!this.isActive)
return false;
if (Date.now() - this.startTime > this.options.defaultTimeout) {
this.isActive = false;
return false;
}
return true;
}
/**
* Starts a new transaction within this session.
* Only one transaction can be active at a time per session.
*
* @returns A new Transaction instance
* @throws Error if session is inactive or another transaction is active
*/
startTransaction() {
if (!this.isSessionActive()) {
throw new Error("Session has expired or is inactive");
}
if (this.currentTransaction) {
throw new Error("A transaction is already active in this session. Commit or rollback first.");
}
this.currentTransaction = new Transaction_service_1.default(this.collectionPath, this.isEncrypted, this.encryptionKey);
return this.currentTransaction;
}
/**
* Executes a callback function within a transaction context.
* Automatically commits on success or rolls back on error.
* Supports automatic retry on transient errors.
*
* @param callback - Async function that receives the transaction and performs operations
* @returns Promise resolving to success/error result
*
* @example
* ```typescript
* const session = collection.startSession();
* await session.withTransaction(async (txn) => {
* await txn.insert({ name: 'Alice' });
* await txn.update({ name: 'Bob' }, { age: 30 });
* });
* ```
*/
withTransaction(callback) {
return __awaiter(this, void 0, void 0, function* () {
if (!this.isSessionActive()) {
return this.ResponseHelper.Error("Session has expired or is inactive");
}
let lastError = null;
let attempts = 0;
while (attempts < this.options.maxRetries) {
attempts++;
try {
// Create a new transaction
const txn = this.startTransaction();
// Execute the callback
yield callback(txn);
// Commit the transaction
const result = yield txn.commit();
// Clear current transaction reference
this.currentTransaction = null;
if ('status' in result && result.status === true) {
return result;
}
// If commit failed but not due to timeout, don't retry
if ('message' in result && !this.isRetryableError(result.message)) {
return result;
}
lastError = result;
}
catch (error) {
// Ensure transaction is rolled back on error
if (this.currentTransaction) {
try {
yield this.currentTransaction.rollback();
}
catch (_a) {
// Ignore rollback errors
}
this.currentTransaction = null;
}
// Check if error is retryable
if (!this.options.retryWrites || !this.isRetryableError(error.message)) {
return this.ResponseHelper.Error(error.message || error);
}
lastError = error;
}
// Wait before retry with exponential backoff
if (attempts < this.options.maxRetries) {
yield this.sleep(Math.min(100 * Math.pow(2, attempts - 1), 1000));
}
}
return this.ResponseHelper.Error(`Transaction failed after ${attempts} attempts: ${(lastError === null || lastError === void 0 ? void 0 : lastError.message) || lastError}`);
});
}
/**
* Commits the current transaction if one is active.
*
* @returns Promise resolving to success/error result
*/
commitTransaction() {
return __awaiter(this, void 0, void 0, function* () {
if (!this.currentTransaction) {
return this.ResponseHelper.Error("No active transaction to commit");
}
try {
const result = yield this.currentTransaction.commit();
this.currentTransaction = null;
return result;
}
catch (error) {
this.currentTransaction = null;
return this.ResponseHelper.Error(error.message || error);
}
});
}
/**
* Rolls back the current transaction if one is active.
*
* @returns Promise resolving to success/error result
*/
abortTransaction() {
return __awaiter(this, void 0, void 0, function* () {
if (!this.currentTransaction) {
return this.ResponseHelper.Error("No active transaction to abort");
}
try {
const result = yield this.currentTransaction.rollback();
this.currentTransaction = null;
return result;
}
catch (error) {
this.currentTransaction = null;
return this.ResponseHelper.Error(error.message || error);
}
});
}
/**
* Ends the session and cleans up resources.
* Any active transaction will be rolled back.
*/
endSession() {
return __awaiter(this, void 0, void 0, function* () {
if (this.currentTransaction) {
try {
yield this.currentTransaction.rollback();
}
catch (_a) {
// Ignore rollback errors during cleanup
}
this.currentTransaction = null;
}
this.isActive = false;
});
}
/**
* Determines if an error is retryable (transient)
*/
isRetryableError(message) {
if (!message)
return false;
const retryablePatterns = [
'timeout',
'lock',
'deadlock',
'conflict',
'busy',
'temporary',
'EBUSY',
'EAGAIN'
];
const lowerMessage = message.toLowerCase();
return retryablePatterns.some(pattern => lowerMessage.includes(pattern));
}
/**
* Simple sleep utility
*/
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
exports.default = Session;
//# sourceMappingURL=Session.service.js.map