NewIntentExtractor / src /lib /mongodb.ts
helo-ayush's picture
feat: deploy SIHJantaParty Realtime Chat & Events Server (Node 20 Docker)
78664f6
Raw History Blame Contribute Delete
4.18 kB
import mongoose, { type ClientSession } from "mongoose";
interface MongooseCache {
conn: typeof mongoose | null;
promise: Promise<typeof mongoose> | null;
}
declare global {
var mongoose: MongooseCache | undefined;
}
let cached = global.mongoose;
if (!cached) {
cached = global.mongoose = { conn: null, promise: null };
}
/**
* Mongoose 9 propagates the active session through AsyncLocalStorage, so
* queries inside withTx() pick it up even if a `{ session }` option is missed.
* The DAL still passes it explicitly — this is a safety net, not the mechanism.
*
* Must be set before connecting.
*/
mongoose.set("transactionAsyncLocalStorage", true);
/**
* autoIndex creates MISSING indexes only — it never alters an existing
* definition, so editing an index in src/models/ is not enough. Run
* `npm run sync-indexes` after any index change. Off in production, where
* building an index on a live collection is a deliberate operation.
*/
mongoose.set("autoIndex", process.env.NODE_ENV !== "production");
export async function connectToDatabase(): Promise<typeof mongoose> {
const MONGO_URI = process.env.MONGODB_URI || process.env.MONGO_URI;
if (!MONGO_URI) {
throw new Error(
"Please define MONGO_URI or MONGODB_URI in your .env.local file"
);
}
if (cached!.conn) {
return cached!.conn;
}
if (!cached!.promise) {
cached!.promise = mongoose
.connect(MONGO_URI, { dbName: "sihjantaparty" })
.then((mongooseInstance) => {
return mongooseInstance;
});
}
try {
cached!.conn = await cached!.promise;
} catch (e) {
cached!.promise = null;
throw e;
}
return cached!.conn;
}
/* ------------------------------------------------------------------ */
/* Transactions */
/* ------------------------------------------------------------------ */
/** MongoDB's error codeName when the deployment has no replica set. */
function isNoTransactionSupport(e: unknown): boolean {
const msg = e instanceof Error ? e.message : String(e);
return (
msg.includes("Transaction numbers are only allowed on a replica set") ||
msg.includes("Transactions are not supported") ||
msg.includes("IllegalOperation")
);
}
/**
* Runs `fn` inside a MongoDB transaction, retrying transient errors (mongoose's
* connection.transaction() handles TransientTransactionError and
* UnknownTransactionCommitResult for us).
*
* WHY THIS WRAPPER EXISTS — read before "fixing" a transaction error locally.
*
* Transactions require a replica set. Atlas is one; a bare local `mongod` is
* not, and it fails with "Transaction numbers are only allowed on a replica set
* member or mongos". The tempting five-second fix is to delete the transaction
* wrapper to unblock local work — which silently ships the non-atomic version,
* whose failure modes (a 7-member team, one user on two teams) are
* unrecoverable data corruption discovered mid-hackathon.
*
* So this fails loudly with instructions instead of degrading. There is no
* fallback path on purpose. To develop locally, run a single-node replica set:
*
* mongod --replSet rs0 --dbpath ./data
* mongosh --eval "rs.initiate()"
*
* or point MONGO_URI at Atlas.
*/
export async function withTx<T>(
fn: (session: ClientSession) => Promise<T>
): Promise<T> {
const conn = await connectToDatabase();
try {
return await conn.connection.transaction(fn);
} catch (e) {
if (isNoTransactionSupport(e)) {
throw new Error(
"This write needs a MongoDB transaction, but the current deployment " +
"is a standalone server. Transactions require a replica set.\n\n" +
" Local: mongod --replSet rs0 --dbpath ./data\n" +
' mongosh --eval "rs.initiate()"\n' +
" Or: point MONGO_URI at your Atlas cluster.\n\n" +
"Do NOT remove the transaction to work around this — the writes it " +
"guards are what stop a team reaching 7 members or a user joining " +
"two teams at once.",
{ cause: e }
);
}
throw e;
}
}
export default connectToDatabase;