MongoDB wrapper for Mongoose with shared multi-database access, optional per-database pools, lifecycle events, and utility operations.
npm install polymongoimport PolyMongo from "polymongo";
import mongoose from "mongoose";
const userSchema = new mongoose.Schema({
name: String,
email: String,
role: String,
});
const wrapper = PolyMongo.createWrapper({
mongoURI: "mongodb://localhost:27017",
defaultDB: "production",
coldStart: true,
retry: 3000,
debug: {
log: true,
logPath: "./logs/polymongo",
logHandler: async (logMessage) => {
console.log(logMessage);
},
},
});
const User = wrapper.wrapModel(mongoose.model("User", userSchema));
await wrapper.connect();
const admins = await User.find({ role: "admin" });
const analyticsUsers = await User.db("analytics").find();wrapper.wrapModel(Model)gives you a model that can switch databases with.db("name").wrapper.on("event", handler)centralizes lifecycle listeners.wrapper.connect()andwrapper.disconnect()make startup and shutdown explicit.wrapper.advexposes the underlying Mongoose connections and module when you need lower-level control.PolyMongo.Types.*makes the main public types easier to discover from IntelliSense.dbSpecificandpool.configure()let you give certain databases their own pool settings.
const wrapper = PolyMongo.createWrapper({
mongoURI: "mongodb://localhost:27017",
defaultDB: "main",
maxPoolSize: 10,
minFreeConnections: 0,
idleTimeoutMS: 300000,
coldStart: true,
retry: 3000,
debug: {
log: true,
logPath: "./logs",
logHandler: async (logMessage) => {
console.log(logMessage);
},
},
dbSpecific: [
{
dbName: "analytics",
mongoURI: "mongodb://analytics-cluster:27017",
options: {
maxConnections: 25,
autoClose: true,
ttl: 300000,
coldStart: true,
},
},
],
});const result = await wrapper.connect();
console.log(result.success); // true
console.log(result.alreadyConnected); // true when eager startup already connected
console.log(result.defaultDB); // configured default database
const disconnected = await wrapper.disconnect();
console.log(disconnected.alreadyDisconnected);Behavior notes:
- If
coldStart: false, PolyMongo starts connecting during wrapper creation. - Calling
wrapper.connect()after that does not reconnect unnecessarily. - If the wrapper is already connected,
connect()returns{ success: true, alreadyConnected: true }. - If the wrapper is disconnected,
connect()initializes the connection and waits until it is ready. - If
retryis set, PolyMongo keeps retrying failed connections on that fixed interval in milliseconds. - If
retryis omitted, PolyMongo does not auto-retry after connection failures or disconnects.
const wrapper = PolyMongo.createWrapper({
mongoURI: "mongodb://localhost:27017",
debug: {
log: true,
logPath: "./logs/polymongo",
logHandler: async (logMessage) => {
// Forward PolyMongo logs to your own system
await myLogger.write(logMessage);
},
},
});Logging behavior:
debug.logturns PolyMongo debug logging on or off.debug.logPathenables file logging through Winston only when you provide a folder path.debug.logHandlerlets you consume each formatted log line yourself.- If
debug.logistruebut nologPathorlogHandleris provided, PolyMongo emits nothing externally. - Top-level
logPathstill works for backward compatibility, butdebug.logPathis the preferred shape.
You do not lose raw Mongoose access by using PolyMongo. The wrapper exposes the same internals it uses through wrapper.adv.
const wrapper = PolyMongo.createWrapper({
mongoURI: "mongodb://localhost:27017",
retry: 3000,
});
const mongooseRef = wrapper.adv.mongoose;
const primaryConnection = wrapper.adv.getPrimaryConnection();
const ensuredPrimary = wrapper.adv.getOrCreatePrimaryConnection();
const activeDbConnection = wrapper.adv.getConnection("analytics");
const sharedDbConnection = wrapper.adv.getSharedConnection("analytics");This is useful when:
- you want direct access to
mongoose.Schema, sessions, plugins, or connection methods - you need a raw connection for an edge case PolyMongo does not wrap directly
- you want PolyMongo convenience without giving up old Mongoose workflows
PolyMongo also exposes discoverable type names under PolyMongo.Types.*.
import PolyMongo from "polymongo";
type WrapperOptions = PolyMongo.Types.wrapperOptions;
type ConnectEvent = PolyMongo.Types.connectEvent;
type AdvancedAccess = PolyMongo.Types.advancedAccess;const UserModel = mongoose.model("User", userSchema);
const User = wrapper.wrapModel(UserModel);
await User.create({ name: "Ava", role: "admin" });
await User.db("archive").find();
await User.db("analytics").aggregate([{ $match: { role: "admin" } }]);Use one event method for all lifecycle hooks:
const unsubscribe = wrapper.on("connect", async (event) => {
console.log(event.name); // "connect"
console.log(event.dbName);
console.log(event.state);
console.log(event.timestamp);
});
wrapper.on("disconnect", (event) => {
console.log("Disconnected from", event.dbName);
});
wrapper.on("error", (event) => {
console.error(event.error.message);
});Supported event names:
"connect""disconnect""error""onDbConnect"as an alias of"connect""onDbDisconnect"as an alias of"disconnect"
Event payload fields:
name: emitted event namedbName: mongoose connection database namereadyState: raw mongoose ready state numberstate: readable state labelconnection: mongoose connection instancetimestamp: ISO timestamperror: included only for"error"
wrapper.pool.configure(["analytics"], {
maxConnections: 30,
autoClose: true,
ttl: 300000,
coldStart: false,
});
await wrapper.pool.connect(["reporting"], {
maxConnections: 15,
});const generalStats = wrapper.stats.general();
const analyticsStats = await wrapper.stats.db("analytics");
const databases = await wrapper.stats.listDatabases();const defaultPing = await wrapper.ping();
const analyticsPing = await wrapper.ping("analytics");const exported = await wrapper.actions.exportDB("production");
await wrapper.actions.importDB("backup", exported);
await wrapper.actions.copyDatabase("production", "staging");
await wrapper.actions.dropDatabase("old_data");Streaming helpers use NDJSON so import can process one record at a time instead of rebuilding one giant JSON document in memory.
const writeStream = wrapper.actions.exportDBStream("analytics");
await wrapper.actions.importDBStream("restored", readStream, {
batchSize: 1000,
stopOnError: false,
});NDJSON stream shape:
{"type":"meta","format":"polymongo.ndjson","version":1,"database":"analytics","exportedAt":"2026-04-15T00:00:00.000Z"}
{"type":"collection","collection":"users"}
{"type":"index","collection":"users","index":{"name":"email_1","key":{"email":1},"unique":true}}
{"type":"document","collection":"users","document":{"_id":"...","email":"a@b.com"}}
{"type":"collectionEnd","collection":"users"}const changes = User.db("production").watch();
changes.on("change", (change) => {
console.log(change);
});
wrapper.actions.closeDBstream("production");
wrapper.actions.closeAllWatches();PolyMongo ships declaration files for:
PolyMongoOptionsWrappedModel<T>- event names and event payloads used by
wrapper.on(...) connect()anddisconnect()result objectswrapper.pool,wrapper.actions,wrapper.stats, andwrapper.ping()return types- advanced access types exposed through
wrapper.adv PolyMongo.Types.*aliases for easier discovery in editors
Because the public methods include JSDoc, editors should show parameter and return help directly in autocomplete.
Old style:
wrapper.onDbConnect((connection) => {});
wrapper.onDbDisconnect((connection) => {});New style:
wrapper.on("connect", (event) => {});
wrapper.on("disconnect", (event) => {});The old wrapper.transaction(...) helper has been removed from the wrapper API.
If you need transactions, use Mongoose sessions directly from your own application flow so session ownership stays explicit.
if (wrapper.isConnected()) {
console.log("Database ready");
}
console.log(wrapper.getConnectionState());
console.log(await wrapper.ping());MIT © Krishnesh Mishra
