Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Correct Database per service + mongoose V8 #370

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/moleculer-db-adapter-mongoose/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"license": "MIT",
"peerDependencies": {
"moleculer": "^0.12.0 || ^0.13.0 || ^0.14.0",
"mongoose": "^6.5.4"
"mongoose": "^7.5.2"
},
"devDependencies": {
"benchmarkify": "^3.0.0",
Expand Down
111 changes: 61 additions & 50 deletions packages/moleculer-db-adapter-mongoose/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,25 @@ const Promise = require("bluebird");
const { ServiceSchemaError, MoleculerError } = require("moleculer").Errors;
const mongoose = require("mongoose");


mongoose.set("strictQuery", true);

class MongooseDbAdapter {

/**
* Creates an instance of MongooseDbAdapter.
* @param {String} uri
* @param {Object?} opts
*
* @param {String} uri - The connection URI for the MongoDB server.
* @param {Object} [mongooseOpts] - Optional mongoose options.
* @param {Object} [opts] - Optional additional options.
* @param {boolean} [opts.replaceVirtualsRefById=true] - Flag indicating whether to replace virtual fields with their referenced document's ID.
* Discussed here : https://github.com/moleculerjs/moleculer-db/pull/354#issuecomment-1736853966
*
* @memberof MongooseDbAdapter
*/
constructor(uri, opts) {
constructor(uri, mongooseOpts, opts = { replaceVirtualsRefById: true }) {
this.uri = uri;
this.opts = opts;
this.mongooseOpts = mongooseOpts;
this.opts = opts
}

/**
Expand All @@ -42,6 +46,12 @@ class MongooseDbAdapter {
this.useNativeMongooseVirtuals = !!service.settings?.useNativeMongooseVirtuals

if (this.service.schema.model) {
/**
* using model here is not a problem because we will dismantle it, and re-create a model with the correct connection later
* note : when creating models before the DB, they're linked to the default connection, and not the current one
* @link https://mongoosejs.com/docs/connections.html#multiple_connections
* @type {Mongoose.Model}
*/
this.model = this.service.schema.model;
} else if (this.service.schema.schema) {
if (!this.service.schema.modelName) {
Expand All @@ -65,55 +75,37 @@ class MongooseDbAdapter {
* @memberof MongooseDbAdapter
*/
connect() {
let conn;

if (this.model) {
/* istanbul ignore next */
if (mongoose.connection.readyState == 1) {
this.db = mongoose.connection;
return Promise.resolve();
} else if (mongoose.connection.readyState == 2) {
conn = mongoose.connection.asPromise();
} else {
conn = mongoose.connect(this.uri, this.opts);
}
} else if (this.schema) {
conn = new Promise(resolve =>{
const c = mongoose.createConnection(this.uri, this.opts);
this.model = c.model(this.modelName, this.schema);
resolve(c);
});
}

return conn.then(() => {
this.conn = mongoose.connection;
return mongoose.createConnection(this.uri, this.mongooseOpts).asPromise().then(conn => {
this.conn = conn;

if (mongoose.connection.readyState != mongoose.connection.states.connected) {
if (this.conn.readyState !== mongoose.connection.states.connected) {
throw new MoleculerError(
`MongoDB connection failed . Status is "${
mongoose.connection.states[mongoose.connection._readyState]
mongoose.states[this.conn._readyState]
}"`
);
}


if(this.model) {
this.model = mongoose.model(this.model["modelName"],this.model["schema"]);
this.model = this.conn.model(this.model["modelName"],this.model["schema"]);
}
if(this.schema) {
this.model = this.conn.model(this.modelName, this.schema);
}

this.db = mongoose.connection.db;
this.db = this.conn.db;

if (!this.db) {
throw new MoleculerError("MongoDB connection failed to get DB object");
}

this.service.logger.info("MongoDB adapter has connected successfully.");


/* istanbul ignore next */
mongoose.connection.on("disconnected", () => this.service.logger.warn("Mongoose adapter has disconnected."));
mongoose.connection.on("error", err => this.service.logger.error("MongoDB error.", err));
mongoose.connection.on("reconnect", () => this.service.logger.info("Mongoose adapter has reconnected."));

this.conn.on("disconnected", () => this.service.logger.warn("Mongoose adapter has disconnected."));
this.conn.on("error", err => this.service.logger.error("MongoDB error.", err));
this.conn.on("reconnect", () => this.service.logger.info("Mongoose adapter has reconnected."));
});
}

Expand All @@ -125,15 +117,13 @@ class MongooseDbAdapter {
* @memberof MongooseDbAdapter
*/
disconnect() {
return new Promise(resolve => {
if (this.db && this.db.close) {
this.db.close(resolve);
} else if (this.conn && this.conn.close) {
this.conn.close(resolve);
} else {
mongoose.connection.close(resolve);
}
});
if (this.db && this.db.close) {
return this.db.close();
} else if (this.conn && this.conn.close) {
return this.conn.close();
} else {
return mongoose.connection.close();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this row is not relevant if we don't "touch" the default connection, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes . ( on the first check, I think opening a connection with populate the default one ... but it's not the case, so this will do nothing ) .

I'll note to remove this ( and maybe add a warning, just in case )

}
}

/**
Expand Down Expand Up @@ -196,7 +186,7 @@ class MongooseDbAdapter {
}

/**
* Get count of filtered entites
* Get count of filtered entities
*
* Available filter props:
* - search
Expand Down Expand Up @@ -375,10 +365,15 @@ class MongooseDbAdapter {
.then(entity => {
const json = entity.toJSON();

if (entity._id && entity._id.toHexString) {
json._id = entity._id.toHexString();
} else if (entity._id && entity._id.toString) {
json._id = entity._id.toString();
json._id = this.convertObjectIdToString(entity._id);

if(this.opts.replaceVirtualsRefById && virtualsToPopulate.length > 0) {
virtualsToPopulate
.map((fieldName) => [fieldName, _.get(this, "model.schema.virtuals", {})[fieldName]])
.filter(([, virtual]) => !!virtual)
.forEach(([field, virtual]) => {
json[field] = this.convertObjectIdToString(entity[virtual.options.localField])
})
}

if (!this.useNativeMongooseVirtuals) {
Expand All @@ -389,6 +384,22 @@ class MongooseDbAdapter {
});
}

/**
* Converts an object id to a string representation.
*
* @param {Object} _id - The object id to convert.
* @return {string|Object} The string representation of the object id. (or the object in case we fail to convert it)
*/
convertObjectIdToString(_id) {
if (_id && _id.toHexString) {
return _id.toHexString();
} else if (_id && _id.toString) {
return _id.toString();
}

return _id
}

/**
* Create a filtered query
* Available filters in `params`:
Expand Down
6 changes: 3 additions & 3 deletions packages/moleculer-db-adapter-mongoose/test/models/posts.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"use strict";

let mongoose = require("mongoose");
let Schema = mongoose.Schema;
const {Schema, model} = require("mongoose");

let PostSchema = new Schema({
title: {
Expand Down Expand Up @@ -33,6 +32,7 @@ PostSchema.index({
});

module.exports = {
Model: mongoose.model("Post", PostSchema),
getModel: (connection) => connection.model("Post", PostSchema),
Model: model("Post", PostSchema),
Schema: PostSchema
};
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ UserSchema.virtual("lastPostWithVotes", {
});

module.exports = {
getModel: (connection) => connection.model("User", UserSchema),
Model: model("User", UserSchema),
Schema: UserSchema,
};
56 changes: 28 additions & 28 deletions packages/moleculer-db-adapter-mongoose/test/unit/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ if (process.versions.node.split(".")[0] < 14) {

let fakeDb = {
on: jest.fn(),
close: jest.fn((fn) => fn()),
close: jest.fn().mockResolvedValue(),
model: jest.fn(() => fakeModel),
};

Expand All @@ -76,7 +76,7 @@ if (process.versions.node.split(".")[0] < 14) {
it("should be created", () => {
expect(adapter).toBeDefined();
expect(adapter.uri).toBe(uri);
expect(adapter.opts).toBe(opts);
expect(adapter.mongooseOpts).toBe(opts);
expect(adapter.init).toBeDefined();
expect(adapter.connect).toBeDefined();
expect(adapter.disconnect).toBeDefined();
Expand Down Expand Up @@ -137,34 +137,31 @@ if (process.versions.node.split(".")[0] < 14) {

describe("Test connect", () => {
beforeEach(() => {
mongoose.connection.readyState =
mongoose.connection.states.disconnected;
mongoose.connect = jest.fn(() => {
mongoose.connection.readyState =
mongoose.connection.states.connected;
return Promise.resolve();
});

mongoose.model = jest.fn(() => fakeModel);

Object.entries(fakeDb).forEach(([k, v]) => {
mongoose.connection[k] = v;
mongoose.createConnection = jest.fn(() => {
return {
asPromise: jest.fn(
() => Promise.resolve({
...fakeDb,
db: fakeDb,
readyState: mongoose.connection.states.connected,
})
)
};
});
mongoose.connection.db = fakeDb;
});

it("call connect with uri", () => {
fakeDb.on.mockClear();

adapter.opts = undefined;
adapter.mongooseOpts = undefined;
adapter.model = jest.fn(() => fakeModel);

return adapter
.connect()
.catch(protectReject)
.then(() => {
expect(mongoose.connect).toHaveBeenCalledTimes(1);
expect(mongoose.connect).toHaveBeenCalledWith(
expect(mongoose.createConnection).toHaveBeenCalledTimes(1);
expect(mongoose.createConnection).toHaveBeenCalledWith(
"mongodb://127.0.0.1",
undefined
);
Expand All @@ -185,7 +182,7 @@ if (process.versions.node.split(".")[0] < 14) {
it("call connect with uri & opts", () => {
fakeDb.on.mockClear();

adapter.opts = {
adapter.mongooseOpts = {
user: "admin",
pass: "123456",
};
Expand All @@ -194,10 +191,10 @@ if (process.versions.node.split(".")[0] < 14) {
.connect()
.catch(protectReject)
.then(() => {
expect(mongoose.connect).toHaveBeenCalledTimes(1);
expect(mongoose.connect).toHaveBeenCalledWith(
expect(mongoose.createConnection).toHaveBeenCalledTimes(1);
expect(mongoose.createConnection).toHaveBeenCalledWith(
adapter.uri,
adapter.opts
adapter.mongooseOpts
);
});
});
Expand Down Expand Up @@ -251,15 +248,18 @@ if (process.versions.node.split(".")[0] < 14) {
adapter.init(broker, service);

mongoose.createConnection = jest.fn(() => {
mongoose.connection.readyState =
mongoose.connection.states.connected;
return {
connection: { db: fakeDb, ...fakeDb },
model: jest.fn(() => fakeModel),
asPromise: jest.fn(
() => Promise.resolve({
...fakeDb,
db: fakeDb,
readyState: mongoose.connection.states.connected,
})
)
};
});

adapter.opts = {
adapter.mongooseOpts = {
user: "admin",
pass: "123456",
};
Expand All @@ -273,7 +273,7 @@ if (process.versions.node.split(".")[0] < 14) {
);
expect(mongoose.createConnection).toHaveBeenCalledWith(
adapter.uri,
adapter.opts
adapter.mongooseOpts
);
expect(adapter.model).toBe(fakeModel);
});
Expand Down
Loading