diff --git a/.eslintrc b/.eslintrc index 711a302..74a2609 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,29 +1,320 @@ { - "extends": "airbnb", + "extends": ["plugin:import/errors", "plugin:import/warnings"], + "settings": { + "import/resolver": { + "node": { + "extensions": [".mjs", ".js", ".json"] + } + }, + "import/extensions": [".js", ".mjs", ".jsx"], + "import/core-modules": [], + "import/ignore": ["node_modules", "\\.(coffee|scss|css|less|hbs|svg|json)$"] + }, "env": { + "es6": true, + "node": true, "mocha": true }, + "parserOptions": { + "ecmaVersion": 12 + }, "rules": { - "no-param-reassign": 0, + "import/no-unresolved": [ + "error", + { "commonjs": true, "caseSensitive": true } + ], + "import/named": "error", + "import/default": "off", + "import/namespace": "off", + "import/export": "error", + "import/no-named-as-default": "error", + "import/no-named-as-default-member": "error", + "import/no-deprecated": "off", + "import/no-extraneous-dependencies": [ + "error", + { + "devDependencies": [ + "test/**", + "tests/**", + "spec/**", + "**/__tests__/**", + "test.{js,jsx}", + "test-*.{js,jsx}", + "**/*.{test,spec}.{js,jsx}", + "**/jest.config.js", + "**/webpack.config.js", + "**/webpack.config.*.js", + "**/rollup.config.js", + "**/rollup.config.*.js", + "**/gulpfile.js", + "**/gulpfile.*.js", + "**/Gruntfile{,.js}", + "**/protractor.conf.js", + "**/protractor.conf.*.js" + ], + "optionalDependencies": false + } + ], + "import/no-mutable-exports": "error", + + "import/no-commonjs": "off", + "import/no-amd": "error", + "import/no-nodejs-modules": "off", + + "import/first": ["error", "absolute-first"], + "import/imports-first": "off", + "import/no-duplicates": "error", + "import/no-namespace": "off", + "import/extensions": [ + "error", + "always", + { + "js": "never", + "mjs": "never", + "jsx": "never" + } + ], + "import/order": [ + "off", + { + "groups": [ + "builtin", + "external", + "internal", + "parent", + "sibling", + "index" + ], + "newlines-between": "never" + } + ], + "import/prefer-default-export": "error", + "import/no-restricted-paths": "off", + "import/max-dependencies": ["off", { "max": 10 }], + "import/no-absolute-path": "error", + "import/no-dynamic-require": "off", + "import/no-internal-modules": [ + "off", + { + "allow": [] + } + ], + "import/unambiguous": "off", + "import/no-webpack-loader-syntax": "error", + "import/no-unassigned-import": "off", + "import/no-named-default": "error", + "import/no-anonymous-default-export": [ + "off", + { + "allowArray": false, + "allowArrowFunction": false, + "allowAnonymousClass": false, + "allowAnonymousFunction": false, + "allowLiteral": false, + "allowObject": false + } + ], + "import/exports-last": "off", + "import/group-exports": "off", + "import/no-default-export": "off", + "import/no-self-import": "off", + "accessor-pairs": 2, + "arrow-spacing": 2, + "block-scoped-var": 2, + "block-spacing": [2, "always"], + "brace-style": [ + 2, + "1tbs", + { + "allowSingleLine": false + } + ], + "camelcase": 0, + "comma-dangle": [ + "error", + { + "arrays": "always-multiline", + "objects": "always-multiline", + "imports": "never", + "exports": "never", + "functions": "ignore" + } + ], + "comma-spacing": [ + 2, + { + "after": true, + "before": false + } + ], + "comma-style": [2, "last"], + "consistent-this": [2, "self"], + "constructor-super": 2, + "curly": 2, + "default-case": 2, + "dot-location": [2, "property"], + "dot-notation": 2, + "eol-last": 2, + "eqeqeq": [2, "smart"], + "generator-star-spacing": [ + "error", + { + "before": false, + "after": true, + "anonymous": { "before": false, "after": true }, + "method": { "before": true, "after": true } + } + ], "global-require": 0, - "prefer-destructuring": 0, - "func-names": 0, - "import/no-extraneous-dependencies": 0, - "no-unused-expressions": 0, + "handle-callback-err": 1, + "indent": [ + 2, + 2, + { + "SwitchCase": 1 + } + ], + "key-spacing": [ + 2, + { + "afterColon": true, + "beforeColon": false + } + ], + "keyword-spacing": [ + "error", + { + "after": true, + "before": true, + "overrides": { + "else": { "before": true }, + "catch": { "before": true } + } + } + ], + "new-parens": 2, + "no-alert": 1, + "no-array-constructor": 2, + "no-bitwise": 2, + "no-caller": 2, + "no-case-declarations": 0, + "no-catch-shadow": 2, + "no-class-assign": 2, + "no-console": 1, + "no-const-assign": 2, + "no-constant-condition": 2, + "no-continue": 2, + "no-control-regex": 2, + "no-debugger": 1, + "no-delete-var": 2, + "no-div-regex": 1, + "no-dupe-class-members": 2, + "no-dupe-keys": 2, + "no-duplicate-case": 2, + "no-else-return": 2, + "no-empty": 2, + "no-empty-character-class": 2, + "no-eval": 2, + "no-extend-native": 2, + "no-extra-bind": 2, + "no-extra-boolean-cast": 2, + "no-extra-semi": 2, + "no-fallthrough": 2, + "no-floating-decimal": 2, + "no-func-assign": 2, + "no-implied-eval": 2, + "no-inner-declarations": 2, + "no-invalid-regexp": 2, + "no-irregular-whitespace": 2, + "no-iterator": 2, + "no-labels": 2, + "no-lone-blocks": 2, + "no-lonely-if": 2, + "no-loop-func": 0, + "no-mixed-spaces-and-tabs": 2, + "no-multi-spaces": 2, + "no-multi-str": 2, + "no-multiple-empty-lines": [ + 2, + { + "max": 2, + "maxEOF": 1 + } + ], + "no-native-reassign": 2, + "no-negated-in-lhs": 2, + "no-nested-ternary": 2, + "no-new": 2, + "no-new-func": 2, + "no-new-object": 2, + "no-new-require": 2, + "no-new-wrappers": 2, + "no-octal": 2, + "no-octal-escape": 2, + "no-param-reassign": 2, + "no-proto": 2, + "no-redeclare": 2, + "no-return-assign": 2, + "no-self-compare": 2, + "no-shadow": 2, + "no-shadow-restricted-names": 2, + "no-spaced-func": 2, + "no-this-before-super": 2, + "no-throw-literal": 2, + "no-trailing-spaces": [ + 2, + { + "skipBlankLines": true + } + ], + "no-undef": "error", + "no-undef-init": 2, + "no-unneeded-ternary": 2, + "no-unreachable": 2, + "no-unused-expressions": [ + 2, + { + "allowShortCircuit": true + } + ], + "no-unused-vars": 2, + "no-use-before-define": 2, + "no-useless-call": 2, + "no-useless-concat": 2, + "no-var": 2, + "no-void": 2, + "no-warning-comments": 1, + "no-with": 2, + "object-curly-spacing": [2, "always"], "prefer-arrow-callback": 0, - "no-underscore-dangle": 0, - "no-unneeded-ternary": 0, - "arrow-body-style": 0, - "no-void": 0, - "no-multiple-empty-lines": 0, - "no-unused-vars": ["error", { - "varsIgnorePattern": "coMocha" - }], - "strict": 0, - "max-len": 0, - "import/no-dynamic-require": 0, - "arrow-parens": 0, - "import/newline-after-import": 0, - "comma-dangle": 0 + "prefer-spread": 1, + "prefer-template": 1, + "quotes": [2, "single", "avoid-escape"], + "radix": [2, "as-needed"], + "require-yield": 2, + "semi": [2, "always"], + "space-before-blocks": [2, "always"], + "space-before-function-paren": [ + 2, + { + "anonymous": "always", + "named": "never" + } + ], + "space-in-parens": ["error", "never"], + "space-infix-ops": 2, + "space-unary-ops": [ + 2, + { + "nonwords": false, + "words": true + } + ], + "spaced-comment": [2, "never"], + "use-isnan": 2, + "valid-typeof": 2, + "vars-on-top": 2, + "wrap-iife": [2, "inside"], + "yoda": 2 } -} \ No newline at end of file +} diff --git a/index.js b/index.js deleted file mode 100644 index 2c8b181..0000000 --- a/index.js +++ /dev/null @@ -1,144 +0,0 @@ - -const { MongoClient, Logger: mLogger, ObjectId } = require('mongodb'); - -/** - * @class Mongo - */ -class Mongo { - /** - * @param {string} name - unique name to this service - * @param {EventEmitter} emitter - * @param {Object} config - configuration object of service - */ - constructor(name, emitter, config) { - this.name = name; - this.emitter = emitter; - this.client = null; - this.config = Object.assign({ - host: 'localhost', - port: 27017, - }, config, { - auth: Object.assign({ - use: false, - }, config.auth), - replica: Object.assign({ - use: false, - }, config.replica), - options: Object.assign({ - keepAlive: true, - useUnifiedTopology: true, - autoReconnect: true, - poolSize: 5, - connectTimeoutMS: 30000, - socketTimeoutMS: 30000, - connectWithNoPrimary: false, - readPreference: 'secondaryPreferred', - }, config.options) - }); - } - - log(message, data) { - this.emitter.emit('log', { - service: this.name, - message, - data, - }); - } - - success(message, data) { - this.emitter.emit('success', { - service: this.name, message, data, - }); - } - - error(err, data) { - this.emitter.emit('error', { - service: this.name, - data, - err, - }); - } - - - /** - * Connect to server - */ - init() { - const { config } = this; - const { auth, options, replica } = config; - - if (this.client) { - return Promise.resolve(this); - } - - const infoObj = {}; - - let url = 'mongodb://'; - if (auth.use === true) { - Object.assign(infoObj, { - authentication: 'TRUE', - }); - url += `${auth.username}:${auth.password}@`; - Object.assign(options, { - authSource: auth.authSource, - }); - } else { - Object.assign(infoObj, { - authentication: 'FALSE', - }); - } - if (replica.use === true) { - Object.assign(infoObj, { - mode: 'REPLICAS', - servers: replica.servers, - }); - url += replica.servers.map(s => `${s.host}:${s.port}`).join(','); - Object.assign(options, { - replicaSet: replica.name, - }); - } else { - Object.assign(infoObj, { - mode: 'SINGLE', - host: config.host, - port: config.port, - }); - url += `${config.host}:${config.port}`; - } - Object.assign(infoObj, { - db: config.db, - options, - }); - - this.log(`Connecting in ${infoObj.mode} mode`, infoObj); - - return MongoClient.connect(url, options).then(client => { - this.client = client.db(config.db); - this.connected = true; - this.success(`Successfully connected in ${infoObj.mode} mode`); - mLogger.setLevel('info'); - mLogger.setCurrentLogger((msg, context) => { - this.log(msg, context); - }); - return this; - }); - } -} - -function isValidObjectId(value) { - const regex = /[0-9a-f]{24}/; - const matched = String(value).match(regex); - if (!matched) { - return false; - } - - return ObjectId.isValid(value); -} - -function castToObjectId(value) { - if (isValidObjectId(value) === false) { - throw new TypeError(`Value passed is not valid objectId, is [ ${value} ]`); - } - return ObjectId.createFromHexString(value); -} - -module.exports = { Mongo, ObjectId, isValidObjectId, castToObjectId }; diff --git a/package.json b/package.json index 37e4de7..90a50c7 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { - "name": "@akshendra/mongo", - "version": "0.1.2", + "name": "@quizizz/mongo", + "version": "0.2.0", "description": "A simple wrapper around mongo native nodejs", - "main": "index.js", + "main": "src/index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, @@ -18,6 +18,14 @@ "author": "Akshendra Pratap Singh", "license": "ISC", "dependencies": { - "mongodb": "3.6.4" + "lodash.clonedeep": "^4.5.0", + "lodash.defaultsdeep": "^4.6.1", + "mongodb": "4.3.0", + "mongoose": "6.2.2" + }, + "devDependencies": { + "@types/node": "^17.0.18", + "eslint": "^7.28.0", + "eslint-plugin-import": "^2.23.4" } } diff --git a/src/connection.d.ts b/src/connection.d.ts new file mode 100644 index 0000000..882612d --- /dev/null +++ b/src/connection.d.ts @@ -0,0 +1,16 @@ +import { MongoClientOptions } from "mongodb"; + +type connectionConfig = { + host?: string; + port?: number; + dbName?: string; + replica?: { + use: boolean; + name: string; + servers: { host: string; port: number }[]; + }; + auth?: { + use: boolean; + }, + options?: MongoClientOptions, +} \ No newline at end of file diff --git a/src/connection.js b/src/connection.js new file mode 100644 index 0000000..13e839c --- /dev/null +++ b/src/connection.js @@ -0,0 +1,56 @@ + +const defaults = require('lodash.defaultsdeep'); +const clone = require('lodash.clonedeep'); + +exports.generateUrl = function (config) { + const { auth, options, replica } = clone(config); + let url = 'mongodb://'; + let print = {}; + + if (auth.use === true) { + print.authentication = 'TRUE'; + url += `${auth.username}:${auth.password}@`; + options.authSource = auth.authSource; + } else { + print.authentication = 'FALSE'; + } + if (replica.use === true) { + print.mode = 'REPLICAS'; + print.servers = replica.servers; + url += replica.servers.map(s => `${s.host}:${s.port}`).join(','); + options.replicaSet = replica.name; + } else { + print.mode = 'SINGLE'; + print.host = config.host; + print.port = config.port; + url += `${config.host}:${config.port}`; + options.directConnection = true; + } + print.db = config.db; + print.options = options; + + return { + url, + options, + print, + }; +}; + +exports.addDefaults = (config) => { + return defaults(config, { + host: 'localhost', + port: 27017, + auth: { + use: false, + }, + replica: { + use: false, + }, + options: { + connectTimeoutMS: 30000, + keepAlive: true, + readPreference: 'secondaryPreferred', + socketTimeoutMS: 30000, + }, + }); +}; diff --git a/src/index.d.ts b/src/index.d.ts new file mode 100644 index 0000000..9733564 --- /dev/null +++ b/src/index.d.ts @@ -0,0 +1,8 @@ + +export * as Mongoose from 'mongoose'; +export * as Mongo from 'mongodb'; + +export * from './mongo'; +export * from './mongoose'; +export * from './utils'; +export * from './connection'; \ No newline at end of file diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..8874e6a --- /dev/null +++ b/src/index.js @@ -0,0 +1,16 @@ + +const { MongoWrapper } = require('./mongo'); +const { MongooseWrapper } = require('./mongoose'); +const Mongoose = require('mongoose'); +const connection = require('./connection'); +const Mongo = require('mongodb'); +const utils = require('./utils'); + +module.exports = { + Mongo, + Mongoose, + ...utils, + ...connection, + MongoWrapper, + MongooseWrapper, +}; diff --git a/src/mongo.d.ts b/src/mongo.d.ts new file mode 100644 index 0000000..074ef30 --- /dev/null +++ b/src/mongo.d.ts @@ -0,0 +1,9 @@ +import { Db } from "mongodb"; +import { EventEmitter } from 'events'; +import { connectionConfig } from "./connection"; + +export class MongoWrapper { + client: Db; + constructor(name: string, emitter: EventEmitter, config: connectionConfig); + init(): Promise +} diff --git a/src/mongo.js b/src/mongo.js new file mode 100644 index 0000000..9eedba9 --- /dev/null +++ b/src/mongo.js @@ -0,0 +1,50 @@ +/// + +const { MongoClient, Logger: mLogger } = require('mongodb'); +const { generateUrl, addDefaults } = require('./connection'); +const Service = require('./service'); + +/** + * @class Mongo + */ +class MongoWrapper extends Service { + /** + * @param {string} name - unique name to this service + * @param {EventEmitter} emitter + * @param {Object} config - configuration object of service + */ + constructor(name, emitter, config) { + super(name, emitter, config); + this.client = null; + this.config = addDefaults(config); + } + + + /** + * Connect to server + */ + init() { + const { config } = this; + + if (this.client) { + return Promise.resolve(this); + } + + const { url, options, print } = generateUrl(config); + + this.log(`Connecting in ${print.mode} mode`, print); + + return MongoClient.connect(url, options).then(client => { + this.client = client.db(config.db); + this.connected = true; + this.success(`Successfully connected in ${print.mode} mode`); + mLogger.setLevel('info'); + mLogger.setCurrentLogger((msg, context) => { + this.log(msg, context); + }); + return this; + }); + } +} + +exports.MongoWrapper = MongoWrapper; diff --git a/src/mongoose.d.ts b/src/mongoose.d.ts new file mode 100644 index 0000000..ee8d2c9 --- /dev/null +++ b/src/mongoose.d.ts @@ -0,0 +1,9 @@ +import { MongoClient } from "mongodb"; +import { EventEmitter } from 'events'; +import { connectionConfig } from "./connection"; + +export class MongooseWrapper { + client: MongoClient; + constructor(name: string, emitter: EventEmitter, config: connectionConfig); + init(): Promise +} diff --git a/src/mongoose.js b/src/mongoose.js new file mode 100644 index 0000000..00e0f4b --- /dev/null +++ b/src/mongoose.js @@ -0,0 +1,37 @@ +/// + +const MongooseClient = require('mongoose'); +const { generateUrl, addDefaults } = require('./connection'); +const Service = require('./service'); + +/** + * @class Mongo + */ +class MongooseWrapper extends Service { + /** + * @param {string} name - unique name to this service + * @param {EventEmitter} emitter + * @param {Object} config - configuration object of service + */ + constructor(name, emitter, config) { + super(name, emitter, config); + this.name = name; + this.emitter = emitter; + this.config = addDefaults(config); + } + + /** + * Connect to server + */ + async init() { + const { config } = this; + const { url, options, print } = generateUrl(config); + + this.log(`Connecting in ${print.mode} mode`, print); + options.dbName = config.db; + await MongooseClient.connect(url, options); + this.log(`Connected in ${print.mode} mode`); + } +} + +exports.MongooseWrapper = MongooseWrapper; diff --git a/src/service.js b/src/service.js new file mode 100644 index 0000000..8bf4298 --- /dev/null +++ b/src/service.js @@ -0,0 +1,32 @@ + +class Service { + constructor(name, emitter, config) { + this.name = name; + this.emitter = emitter; + this.config = config; + } + + log(message, data = {}) { + this.emitter.emit('log', { + service: this.name, + message, + data, + }); + } + + success(message, data = {}) { + this.emitter.emit('success', { + service: this.name, message, data, + }); + } + + error(err, data = {}) { + this.emitter.emit('error', { + service: this.name, + data, + err, + }); + } +} + +module.exports = Service; diff --git a/src/utils.d.ts b/src/utils.d.ts new file mode 100644 index 0000000..41afa79 --- /dev/null +++ b/src/utils.d.ts @@ -0,0 +1,5 @@ +import { ObjectId } from "mongodb"; + +export function isValidObjectId(value: unknown): boolean; +export function castToObjectId(value: string | ObjectId): ObjectId | never; + diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..be72d81 --- /dev/null +++ b/src/utils.js @@ -0,0 +1,19 @@ + +const { ObjectId } = require('mongodb'); + +exports.isValidObjectId = function (value) { + const regex = /[0-9a-f]{24}/; + const matched = String(value).match(regex); + if (!matched) { + return false; + } + + return ObjectId.isValid(value); +}; + +exports.castToObjectId = function (value) { + if (exports.isValidObjectId(value) === false) { + throw new TypeError(`Value passed is not valid objectId, is [ ${value} ]`); + } + return ObjectId.createFromHexString(value); +}; diff --git a/test.js b/tests/mongo.spec.js similarity index 66% rename from test.js rename to tests/mongo.spec.js index b79d566..551e597 100644 --- a/test.js +++ b/tests/mongo.spec.js @@ -7,16 +7,14 @@ emitter.on('error', console.log.bind(console)); emitter.on('log', console.error.bind(console)); emitter.on('success', console.log.bind(console)); -const mongo = new Mongo('mongo', emitter, { - db: 'somedb', -}); +const mongo = new Mongo('mongo', emitter, JSON.parse(process.env.MONGO_CONFIG)); mongo.init() .then(() => { console.log('Connected'); - return mongo.client.collection('inserts').insertOne({ a:1 }); + return mongo.client.collection('inserts').insertOne({ a: 1 }); }) .then(() => { - return mongo.client.collection('inserts').find({}).toArray(); + return mongo.client.collection('inserts').find({}).limit(10).toArray(); }) .then(docs => { console.log(docs);