@warlock.js/cascade
Version:
ORM for managing databases
165 lines (164 loc) • 5.39 kB
JavaScript
import {AsyncLocalStorage}from'async_hooks';const ROLLBACK_SYMBOL = Symbol("rollback");
const COMMIT_SYMBOL = Symbol("commit");
class Database {
/**
* MongoDB Internal Database instance
*/
database;
/**
* Current Connection
*/
connection;
sessionsContainer = new AsyncLocalStorage();
/**
* Execute operations within a transaction (automatic commit/rollback)
*
* This is the recommended method for most use cases.
* Automatically commits on success, rolls back on errors.
*
* @example
* const user = await database.transaction(async (session) => {
* const user = await User.create({ name: "John" }, { session });
* const order = await Order.create({ userId: user.id }, { session });
* return user; // Auto commits on success
* });
*/
async transaction(callback, sessionOptions = {
defaultTransactionOptions: {
readPreference: "primary",
readConcern: { level: "local" },
writeConcern: { w: "majority" },
},
}) {
const session = this.connection.client.startSession(sessionOptions);
try {
const result = await this.sessionsContainer.run({
session,
database: this,
}, async () => {
return await session.withTransaction(async () => {
return await callback(session);
});
});
return result;
}
finally {
// Always end session after withTransaction completes
await session.endSession();
}
}
/**
* Start a transaction with manual control
*
* Use this when you need explicit control over commit/rollback based on business logic.
* Returns true if committed, false if rolled back.
*
* @example
* const committed = await database.startTransaction(async ({ session, commit, rollback }) => {
* const user = await User.create({ name: "John" }, { session });
* const order = await Order.create({ userId: user.id }, { session });
*
* // Conditional rollback based on business logic
* if (order.total > 10000) {
* console.log("Order too large, rolling back");
* return rollback; // Explicit rollback
* }
*
* if (user.isBanned) {
* console.log("User banned, rolling back");
* return rollback; // Explicit rollback
* }
*
* return commit; // Explicit commit
* });
*
* if (committed) {
* console.log("Transaction committed successfully");
* } else {
* console.log("Transaction was rolled back");
* }
*/
async startTransaction(callback, sessionOptions = {
defaultTransactionOptions: {
readPreference: "primary",
readConcern: { level: "local" },
writeConcern: { w: "majority" },
},
}) {
const session = this.connection.client.startSession(sessionOptions);
try {
let shouldRollback = false;
await this.sessionsContainer.run({
session,
database: this,
}, async () => {
await session.withTransaction(async () => {
const output = await callback({
session,
commit: COMMIT_SYMBOL,
rollback: ROLLBACK_SYMBOL,
});
if (output === ROLLBACK_SYMBOL) {
shouldRollback = true;
// Throw error to trigger rollback in withTransaction
throw new Error("TRANSACTION_ROLLBACK_REQUESTED");
}
// Return value for withTransaction to commit
return output;
});
});
return !shouldRollback; // true if committed, false if rolled back
}
catch (error) {
// Check if it was an intentional rollback
if (error?.message === "TRANSACTION_ROLLBACK_REQUESTED") {
return false; // Rolled back successfully
}
// Re-throw actual errors
throw error;
}
finally {
// Always end session after withTransaction completes
await session.endSession();
}
}
/**
* Get active session
*/
getActiveSession() {
return this.sessionsContainer.getStore();
}
/**
* Set connection instance
*/
setConnection(connection) {
this.connection = connection;
return this;
}
/**
* Set database instance
*/
setDatabase(database) {
this.database = database;
return this;
}
/**
* Get database collection instance
*/
collection(collection) {
return this.database.collection(collection);
}
/**
* List collection names
*/
async listCollectionNames() {
return (await this.database.listCollections().toArray()).map(collection => collection.name);
}
/**
* Drop database
*/
async drop() {
return await this.database.dropDatabase();
}
}
const database = new Database();export{Database,database};//# sourceMappingURL=database.js.map