├── .github └── workflows │ ├── dist.yml │ ├── publish.yml │ └── tests.yml ├── .gitignore ├── LICENSE ├── README.md ├── babel.config.js ├── jest.config.js ├── jest.setup.js ├── package-lock.json ├── package.json ├── scripts └── cleanup-dusts.js ├── src ├── collection.ts ├── index.ts ├── types.ts └── utils.ts ├── tests ├── mongodb.ts └── simple.test.ts └── tsconfig.json /.github/workflows/dist.yml: -------------------------------------------------------------------------------- 1 | name: Create Distribution 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | jobs: 10 | testing: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | 15 | - name: Create Environment File 16 | run: echo "${{ secrets.ENVIRONMENT }}" > .env 17 | 18 | - name: Use Node.js 18.x 19 | uses: actions/setup-node@v3 20 | with: 21 | node-version: 18.x 22 | cache: 'npm' 23 | - run: npm ci 24 | - run: npm test 25 | 26 | build: 27 | needs: testing 28 | runs-on: ubuntu-latest 29 | steps: 30 | - uses: actions/checkout@v3 31 | - uses: actions/setup-node@v3 32 | with: 33 | node-version: 18 34 | registry-url: https://registry.npmjs.org/ 35 | - run: npm ci 36 | - run: npm run build 37 | 38 | - name: Clean up dist version 39 | run: | 40 | node scripts/cleanup-dusts.js && rm -rf scripts 41 | sed -i 's/"main": "src\/index.ts"/"main": "src\/index.js",\n "types": "src\/index.d.ts"/' package.json 42 | sed -i '/"tsconfig.json"/d' package.json 43 | sed -i '/"build":/d' package.json 44 | sed -i '/"test":/d' package.json 45 | 46 | - name: Setup Git for Commit 47 | run: | 48 | git config --global user.name "GitHub Actions" 49 | git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" 50 | git add . 51 | git commit -m "Update dist version" 52 | 53 | - name: Commit and Push 54 | uses: ad-m/github-push-action@master 55 | with: 56 | github_token: ${{ secrets.RUNNER_TOKEN }} 57 | branch: dist 58 | force: true 59 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish Package 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | 13 | - name: Create Environment File 14 | run: echo "${{ secrets.ENVIRONMENT }}" > .env 15 | 16 | - name: Do CI Stuff 17 | uses: actions/setup-node@v3 18 | with: 19 | node-version: 18 20 | - run: npm ci 21 | - run: npm test 22 | 23 | publish-npm: 24 | needs: build 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@v3 28 | - uses: actions/setup-node@v3 29 | with: 30 | node-version: 18 31 | registry-url: https://registry.npmjs.org/ 32 | - run: npm ci 33 | - run: npm publish 34 | env: 35 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 36 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Test Suite 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | jobs: 10 | testing: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | 16 | - name: Create Environment File 17 | run: echo "${{ secrets.ENVIRONMENT }}" > .env 18 | 19 | - name: Use Node.js 18.x 20 | uses: actions/setup-node@v3 21 | with: 22 | node-version: 18.x 23 | cache: 'npm' 24 | - run: npm ci 25 | - run: npm run build --if-present 26 | - run: npm test 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | # JetBrains WebStorm 107 | .idea 108 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Litehex 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MongoDB driver for Node.js 2 | 3 | This project uses the official MongoDB driver for Node.js. It is a wrapper around the official driver that provides a more convenient API for use with TypeScript and JavaScript. 4 | 5 | ### Installation 6 | 7 | ```bash 8 | npm install github:litehex/mongodb#dist 9 | ``` 10 | 11 | ### Usage 12 | 13 | #### Connecting to MongoDB 14 | 15 | ```typescript 16 | import { MongoDB, InitMongo } from "@litehex/mongodb"; 17 | 18 | InitMongo({ 19 | hostname: "localhost", 20 | port: 27017, 21 | username: "root", 22 | password: "password" 23 | }); 24 | 25 | export const db = MongoDB.db("testing"); 26 | ``` 27 | 28 | #### Creating a collection 29 | 30 | ```typescript 31 | import { OptionalId, CollectionConfig } from "@litehex/mongodb"; 32 | import Collection from "@litehex/mongodb/collection"; 33 | 34 | export type User = OptionalId<{ 35 | nickname: string; 36 | email: string; 37 | }> 38 | 39 | export class UsersCollection extends Collection { 40 | getConfig(): CollectionConfig { 41 | return { 42 | name: "users", 43 | database: "testing" 44 | } 45 | } 46 | 47 | static doSomething() { 48 | console.log("Something"); 49 | } 50 | } 51 | ``` 52 | 53 | ### License 54 | 55 | This project is licensed under the MIT license. See the [LICENSE](LICENSE) file for more info. 56 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ['@babel/preset-env', {targets: {node: 'current'}}], 4 | '@babel/preset-typescript', 5 | ], 6 | }; 7 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * For a detailed explanation regarding each configuration property, visit: 3 | * https://jestjs.io/docs/configuration 4 | */ 5 | 6 | module.exports = { 7 | // All imported modules in your tests should be mocked automatically 8 | // automock: false, 9 | 10 | // Stop running tests after `n` failures 11 | // bail: 0, 12 | 13 | // The directory where Jest should store its cached dependency information 14 | // cacheDirectory: "C:\\Users\\shahr\\AppData\\Local\\Temp\\jest", 15 | 16 | // Automatically clear mock calls, instances, contexts and results before every test 17 | // clearMocks: false, 18 | 19 | // Indicates whether the coverage information should be collected while executing the test 20 | // collectCoverage: false, 21 | 22 | // An array of glob patterns indicating a set of files for which coverage information should be collected 23 | // collectCoverageFrom: undefined, 24 | 25 | // The directory where Jest should output its coverage files 26 | // coverageDirectory: undefined, 27 | 28 | // An array of regexp pattern strings used to skip coverage collection 29 | // coveragePathIgnorePatterns: [ 30 | // "\\\\node_modules\\\\" 31 | // ], 32 | 33 | // Indicates which provider should be used to instrument code for coverage 34 | coverageProvider: "v8", 35 | 36 | // A list of reporter names that Jest uses when writing coverage reports 37 | // coverageReporters: [ 38 | // "json", 39 | // "text", 40 | // "lcov", 41 | // "clover" 42 | // ], 43 | 44 | // An object that configures minimum threshold enforcement for coverage results 45 | // coverageThreshold: undefined, 46 | 47 | // A path to a custom dependency extractor 48 | // dependencyExtractor: undefined, 49 | 50 | // Make calling deprecated APIs throw helpful error messages 51 | // errorOnDeprecated: false, 52 | 53 | // The default configuration for fake timers 54 | // fakeTimers: { 55 | // "enableGlobally": false 56 | // }, 57 | 58 | // Force coverage collection from ignored files using an array of glob patterns 59 | // forceCoverageMatch: [], 60 | 61 | // A path to a module which exports an async function that is triggered once before all test suites 62 | // globalSetup: undefined, 63 | 64 | // A path to a module which exports an async function that is triggered once after all test suites 65 | // globalTeardown: undefined, 66 | 67 | // A set of global variables that need to be available in all test environments 68 | // globals: {}, 69 | 70 | // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. 71 | // maxWorkers: "50%", 72 | 73 | // An array of directory names to be searched recursively up from the requiring module's location 74 | // moduleDirectories: [ 75 | // "node_modules" 76 | // ], 77 | 78 | // An array of file extensions your modules use 79 | // moduleFileExtensions: [ 80 | // "js", 81 | // "mjs", 82 | // "cjs", 83 | // "jsx", 84 | // "ts", 85 | // "tsx", 86 | // "json", 87 | // "node" 88 | // ], 89 | 90 | // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module 91 | // moduleNameMapper: {}, 92 | 93 | // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader 94 | // modulePathIgnorePatterns: [], 95 | 96 | // Activates notifications for test results 97 | // notify: false, 98 | 99 | // An enum that specifies notification mode. Requires { notify: true } 100 | // notifyMode: "failure-change", 101 | 102 | // A preset that is used as a base for Jest's configuration 103 | // preset: undefined, 104 | 105 | // Run tests from one or more projects 106 | // projects: undefined, 107 | 108 | // Use this configuration option to add custom reporters to Jest 109 | // reporters: undefined, 110 | 111 | // Automatically reset mock state before every test 112 | // resetMocks: false, 113 | 114 | // Reset the module registry before running each individual test 115 | // resetModules: false, 116 | 117 | // A path to a custom resolver 118 | // resolver: undefined, 119 | 120 | // Automatically restore mock state and implementation before every test 121 | // restoreMocks: false, 122 | 123 | // The root directory that Jest should scan for tests and modules within 124 | // rootDir: undefined, 125 | 126 | // A list of paths to directories that Jest should use to search for files in 127 | // roots: [ 128 | // "" 129 | // ], 130 | 131 | // Allows you to use a custom runner instead of Jest's default test runner 132 | // runner: "jest-runner", 133 | 134 | // The paths to modules that run some code to configure or set up the testing environment before each test 135 | // setupFiles: [], 136 | 137 | // A list of paths to modules that run some code to configure or set up the testing framework before each test 138 | setupFilesAfterEnv: [ 139 | "/jest.setup.js" 140 | ], 141 | 142 | // The number of seconds after which a test is considered as slow and reported as such in the results. 143 | // slowTestThreshold: 5, 144 | 145 | // A list of paths to snapshot serializer modules Jest should use for snapshot testing 146 | // snapshotSerializers: [], 147 | 148 | // The test environment that will be used for testing 149 | // testEnvironment: "jest-environment-node", 150 | 151 | // Options that will be passed to the testEnvironment 152 | // testEnvironmentOptions: {}, 153 | 154 | // Adds a location field to test results 155 | // testLocationInResults: false, 156 | 157 | // The glob patterns Jest uses to detect test files 158 | // testMatch: [ 159 | // "**/__tests__/**/*.[jt]s?(x)", 160 | // "**/?(*.)+(spec|test).[tj]s?(x)" 161 | // ], 162 | 163 | // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped 164 | // testPathIgnorePatterns: [ 165 | // "\\\\node_modules\\\\" 166 | // ], 167 | 168 | // The regexp pattern or array of patterns that Jest uses to detect test files 169 | // testRegex: [], 170 | 171 | // This option allows the use of a custom results processor 172 | // testResultsProcessor: undefined, 173 | 174 | // This option allows use of a custom test runner 175 | // testRunner: "jest-circus/runner", 176 | 177 | // A map from regular expressions to paths to transformers 178 | // transform: undefined, 179 | 180 | // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation 181 | // transformIgnorePatterns: [ 182 | // "\\\\node_modules\\\\", 183 | // "\\.pnp\\.[^\\\\]+$" 184 | // ], 185 | 186 | // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them 187 | // unmockedModulePathPatterns: undefined, 188 | 189 | // Indicates whether each individual test should be reported during the run 190 | // verbose: undefined, 191 | 192 | // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode 193 | // watchPathIgnorePatterns: [], 194 | 195 | // Whether to use watchman for file crawling 196 | // watchman: true, 197 | }; 198 | -------------------------------------------------------------------------------- /jest.setup.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config({ 2 | path: '.env', 3 | }); 4 | 5 | process.env.NODE_ENV = 'development'; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@litehex/mongodb", 3 | "version": "1.0.0", 4 | "description": "A MongoDB driver for Node.js", 5 | "main": "src/index.ts", 6 | "scripts": { 7 | "build": "rimraf dist && tsc -p tsconfig.json", 8 | "test": "jest", 9 | "test:watch": "jest --watch" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/litehex/mongodb.git" 14 | }, 15 | "keywords": [ 16 | "mongodb", 17 | "mongo", 18 | "database", 19 | "driver", 20 | "node" 21 | ], 22 | "author": "Litehex ", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/litehex/mongodb/issues" 26 | }, 27 | "files": [ 28 | "src", 29 | "package.json", 30 | "tsconfig.json", 31 | "README.md", 32 | "LICENSE" 33 | ], 34 | "exports": { 35 | ".": "./src/index.ts", 36 | "./collection": "./src/collection.ts", 37 | "./package.json": "./package.json" 38 | }, 39 | "esnext": { 40 | "main": "src/index.js" 41 | }, 42 | "homepage": "https://github.com/litehex/mongodb#readme", 43 | "devDependencies": { 44 | "@babel/core": "^7.20.5", 45 | "@babel/preset-env": "^7.20.2", 46 | "@babel/preset-typescript": "^7.18.6", 47 | "@faker-js/faker": "^7.6.0", 48 | "@jest/globals": "^29.3.1", 49 | "@types/node": "^18.11.9", 50 | "babel-jest": "^29.3.1", 51 | "chalk": "^4.1.2", 52 | "dotenv": "^16.0.3", 53 | "jest": "^29.3.1", 54 | "nodemon": "^2.0.20", 55 | "rimraf": "^3.0.2", 56 | "ts-node": "^10.9.1", 57 | "typescript": "^4.9.3" 58 | }, 59 | "dependencies": { 60 | "mongodb": "4.12.1" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /scripts/cleanup-dusts.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const chalk = require('chalk'); 3 | const rimraf = require('rimraf'); 4 | 5 | const dusts = [ 6 | 'tsconfig.json', 7 | '.github', 8 | 'tests', 9 | 'src', 10 | 'jest*', 11 | 'babel*', 12 | '.env*' 13 | ]; 14 | 15 | console.log(chalk.yellow('Cleaning up dusts...')); 16 | 17 | dusts.forEach(dust => { 18 | rimraf.sync(dust); 19 | console.log(chalk.green(`Removed ${dust}`)); 20 | }); 21 | 22 | console.log(chalk.green('Moving files...')); 23 | 24 | fs.renameSync('dist', 'src'); 25 | 26 | console.log(chalk.yellow('Updating package.json...')); 27 | 28 | const packageJson = require('../package.json'); 29 | 30 | delete packageJson.devDependencies; 31 | 32 | packageJson.exports = { 33 | '.': "./src/index.js", 34 | './collection': "./src/collection.js", 35 | './package.json': "./package.json" 36 | }; 37 | 38 | packageJson.typesVersions = { 39 | '*': { 40 | "collection": ["src/collection.d.ts"] 41 | } 42 | } 43 | 44 | fs.writeFileSync( 45 | 'package.json', 46 | JSON.stringify(packageJson, null, 2) 47 | ); 48 | 49 | console.log(chalk.green('Done!')); 50 | -------------------------------------------------------------------------------- /src/collection.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BulkWriteOptions, 3 | Collection, 4 | CollectionOptions, 5 | Db, 6 | DeleteOptions, 7 | DeleteResult, 8 | Document, 9 | Filter as MongoFilter, 10 | FindOptions, 11 | InsertManyResult, 12 | InsertOneOptions, 13 | InsertOneResult, 14 | MongoClient, 15 | MongoError, 16 | UpdateOptions 17 | } from 'mongodb'; 18 | import MongoDB, { Filter, FindCursor, LeastOne, OptionalId, UpdateQuery, UpdateResult } from "./index"; 19 | 20 | 21 | export interface CollectionConfig extends CollectionOptions { 22 | name: string; 23 | database: string | Db; 24 | client?: MongoClient; 25 | // Timestamps - By default, the collection will add createdAt and updatedAt fields to the documents 26 | timestamps?: boolean; 27 | // Timestamps (format) - By default, the collection will use the Millis format for the timestamps 28 | timestampsFormat?: 'ISODate' | 'Millis' | 'Unix' | 'Date' | 'Utc'; 29 | // Timestamps (fields) - By default, the collection will use createdAt and updatedAt fields for the timestamps 30 | timestampsFields?: LeastOne<{ 31 | createdAt: string; 32 | updatedAt: string; 33 | }>; 34 | } 35 | 36 | /** 37 | * The MongoCollection class is a abstraction of the MongoDB Collection class. 38 | * It provides a easy way to create and manage collections. 39 | * 40 | * @public 41 | * 42 | * @example 43 | * import MongoCollection from "@litehex/mongodb/dist/collection"; 44 | * 45 | * export class Reservations extends MongoCollection { 46 | * 47 | * getConfig(): CollectionConfig { 48 | * return { 49 | * name: "reservations", 50 | * database: "example", 51 | * } 52 | * } 53 | * 54 | * static doSomething() { 55 | * this.collection().find({}).toArray(); 56 | * } 57 | * 58 | * } 59 | */ 60 | export default abstract class MongoCollection { 61 | 62 | private static _updateTimestamps(document: Document, isInsert: boolean) { 63 | if (this._getConfig().timestamps) { 64 | 65 | const format = this._getConfig().timestampsFormat || 'Millis'; 66 | const formattedDate = this._formatDate(new Date(), format); 67 | 68 | const fields = this._getConfig().timestampsFields || { 69 | createdAt: 'createdAt', 70 | updatedAt: 'updatedAt' 71 | }; 72 | 73 | if (isInsert && fields.createdAt && !document[fields.createdAt]) { 74 | document[fields.createdAt] = formattedDate; 75 | } 76 | 77 | if (isInsert && fields.updatedAt && !document[fields.updatedAt]) { 78 | document[fields.updatedAt] = formattedDate; 79 | } 80 | 81 | if (!isInsert && fields.updatedAt) { 82 | if (!document['$set']) { 83 | document['$set'] = {}; 84 | } 85 | 86 | if (!document['$set'][fields.updatedAt]) { 87 | document['$set'][fields.updatedAt] = formattedDate; 88 | } 89 | } 90 | 91 | } 92 | return document; 93 | } 94 | 95 | /** 96 | * Date Formats: 97 | * 98 | * ISODate: "2022-12-05T04:14:52.618Z" 99 | * Date: 2022-12-05T04:14:52.618Z 100 | * Unix: 163869729261 101 | * Millis: 1638697292618 102 | * Utc: "Mon, 05 Dec 2022 04:14:52 GMT" 103 | * 104 | * @param {Date} date The date to format. 105 | * @param {string} format The format to use. 106 | * @private 107 | */ 108 | private static _formatDate(date: Date, format: 'ISODate' | 'Millis' | 'Unix' | 'Date' | 'Utc') { 109 | switch (format) { 110 | case 'ISODate': 111 | return date.toISOString(); 112 | case 'Date': 113 | return date; 114 | case 'Unix': 115 | return Math.floor(date.getTime() / 1000); 116 | case 'Millis': 117 | return date.getTime(); 118 | case 'Utc': 119 | return date.toUTCString(); 120 | } 121 | } 122 | 123 | /** 124 | * Get the collection configuration. 125 | * 126 | * **NOTE:** This method must be implemented in the child class. 127 | * 128 | * @returns {CollectionConfig} The collection configuration. 129 | */ 130 | abstract getConfig(): CollectionConfig; 131 | 132 | /** 133 | * Get the collection configuration. 134 | * @private 135 | */ 136 | private static _getConfig(): CollectionConfig { 137 | if (!this.prototype.getConfig) { 138 | throw new MongoError("The getConfig() method is not implemented in the child class"); 139 | } 140 | return this.prototype.getConfig(); 141 | } 142 | 143 | /** 144 | * Get the collection instance. 145 | * 146 | * @returns {Collection} The collection instance. 147 | */ 148 | static getCollection(): Collection { 149 | return this.getDb().collection(this.getCollectionName()); 150 | } 151 | 152 | /** 153 | * Get the collection name. 154 | * 155 | * @returns {string} The collection name. 156 | */ 157 | static getCollectionName(): string { 158 | return this._getConfig().name; 159 | } 160 | 161 | /** 162 | * Get the database name of the collection. 163 | * 164 | * @returns {string} The database name. 165 | */ 166 | static getDbName(): string { 167 | 168 | if (typeof this._getConfig().database === "string") { 169 | return this._getConfig().database as string; 170 | } 171 | 172 | if (this._getConfig().database instanceof Db) { 173 | return (this._getConfig().database as Db).databaseName; 174 | } 175 | 176 | throw new Error("Invalid typeof database name"); 177 | } 178 | 179 | /** 180 | * Get the database instance of the collection. 181 | * 182 | * @returns {Db} The database instance. 183 | */ 184 | static getDb(): Db { 185 | const { client } = this._getConfig(); 186 | 187 | if (client) { 188 | return client.db(this.getDbName()); 189 | } 190 | 191 | return MongoDB.db(this.getDbName()); 192 | } 193 | 194 | /** 195 | * Get One document from the collection. 196 | * 197 | * @example 198 | * 199 | * const user = await Users.findOne ({ _id: "123" }); 200 | * 201 | * @param {Filter} filter The filter to use. 202 | * @param {FindOptions} options The options to use. 203 | * 204 | * @returns {Promise> | null>} The document. 205 | */ 206 | static async findOne(filter: Filter, options?: FindOptions): Promise { 207 | return this.getCollection().findOne(filter as MongoFilter, options); 208 | } 209 | 210 | /** 211 | * Get many documents from the collection. 212 | * 213 | * @example 214 | * 215 | * const user = await Users.find ({ _id: "123" }); 216 | * 217 | * @param {Filter} filter The filter to use. 218 | * @param {FindOptions} options The options to use. 219 | * 220 | * @returns {Promise>} The cursor. 221 | */ 222 | static async find(filter: Filter, options?: FindOptions): Promise> { 223 | return this.getCollection().find(filter as MongoFilter, options); 224 | } 225 | 226 | /** 227 | * Insert one document into the collection. 228 | * 229 | * @example 230 | * 231 | * const user = await Users.insertOne ({ _id: "123", name: "John Doe" }); 232 | * 233 | * @param {TSchema} document The document to insert. 234 | * @param {InsertOneOptions} options The options to use. 235 | * 236 | * @returns {Promise>} The result. 237 | */ 238 | static async insertOne(document: OptionalId, options?: InsertOneOptions): Promise> { 239 | return this.getCollection().insertOne(this._updateTimestamps(document, true), options as InsertOneOptions); 240 | } 241 | 242 | /** 243 | * Insert many documents into the collection. 244 | * 245 | * @example 246 | * 247 | * const user = await Users.insertMany ([{ _id: "123", name: "John Doe" }, { _id: "123", name: "John Doe" }]); 248 | * 249 | * @param {TSchema[]} documents The documents to insert. 250 | * @param {BulkWriteOptions} options The options to use. 251 | * 252 | * @returns {Promise>} The result. 253 | */ 254 | static async insertMany(documents: OptionalId[], options?: BulkWriteOptions): Promise> { 255 | let newDocuments = documents.map(document => this._updateTimestamps(document, true)); 256 | return this.getCollection().insertMany(newDocuments, options as BulkWriteOptions); 257 | } 258 | 259 | /** 260 | * Update one document in the collection. 261 | * 262 | * @example 263 | * 264 | * const user = await Users.updateOne ({ _id: "123" }, { $set: { name: "John Doe" } }); 265 | * 266 | * @param {Filter} filter The filter to use. 267 | * @param {UpdateQuery} update The update to use. 268 | * 269 | * @returns {Promise} The result. 270 | */ 271 | static async updateOne(filter: Filter, update: UpdateQuery): Promise { 272 | return this.getCollection().updateOne(filter as MongoFilter, this._updateTimestamps(update, false)); 273 | } 274 | 275 | /** 276 | * Update many documents in the collection. 277 | * 278 | * @example 279 | * 280 | * const user = await Users.updateMany ({ _id: "123" }, { $set: { name: "John Doe" } }); 281 | * 282 | * @param {Filter} filter The filter to use. 283 | * @param {UpdateQuery} update The update to use. 284 | * @param {UpdateOptions} options The options to use. 285 | * 286 | * @returns {Promise} The result. 287 | */ 288 | static async updateMany(filter: Filter, update: UpdateQuery, options?: UpdateOptions): Promise { 289 | return this.getCollection().updateMany(filter as MongoFilter, this._updateTimestamps(update, false), options as UpdateOptions); 290 | } 291 | 292 | /** 293 | * Delete one document from the collection. 294 | * 295 | * @example 296 | * 297 | * const user = await Users.deleteOne ({ _id: "123" }); 298 | * 299 | * @param {Filter} filter The filter to use. 300 | * @param {DeleteOptions} options The options to use. 301 | * 302 | * @returns {Promise} The result. 303 | */ 304 | static async deleteOne(filter: Filter, options?: DeleteOptions): Promise { 305 | return this.getCollection().deleteOne(filter as MongoFilter, options as DeleteOptions); 306 | } 307 | 308 | /** 309 | * Delete many documents from the collection. 310 | * 311 | * @example 312 | * 313 | * const user = await Users.deleteMany ({ _id: "123" }); 314 | * 315 | * @param {Filter} filter The filter to use. 316 | * @param {DeleteOptions} options The options to use. 317 | * 318 | * @returns {Promise} The result. 319 | */ 320 | static async deleteMany(filter: Filter, options?: DeleteOptions): Promise { 321 | return this.getCollection().deleteMany(filter as MongoFilter, options as DeleteOptions); 322 | } 323 | 324 | } 325 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { Db, MongoClient, MongoClientOptions, MongoError, MongoRuntimeError } from "mongodb"; 2 | import * as Utils from "./utils"; 3 | import { makeConnectionString } from "./utils"; 4 | import { MongoConfig } from "./types"; 5 | 6 | let _mongodb: MongoClient | undefined; 7 | let _mongoConfig: MongoConfig | undefined; 8 | const _instances: Record = {}; 9 | 10 | /** 11 | * Initialize the MongoDB client and automatically connect to the database 12 | * 13 | * @example 14 | * 15 | * import { InitMongo, MongoDB } from "@litehex/mongodb"; 16 | * 17 | * InitMongo({ 18 | * hostname: 'localhost', 19 | * username: 'root', 20 | * password: '123456', 21 | * }) 22 | * 23 | * // Also you can export the MongoDB client to use it in other files 24 | * export { MongoDB } from "@litehex/mongodb"; 25 | * 26 | * @param {MongoConfig} config 27 | * @returns void 28 | */ 29 | export function InitMongo(config: MongoConfig): void { 30 | 31 | _mongoConfig = config; 32 | 33 | const options: MongoClientOptions = {}; 34 | 35 | const tempConfig: any = { ...config }; 36 | 37 | const keys = [ 38 | 'hostname', 39 | 'port', 40 | 'username', 41 | 'password', 42 | 'database', 43 | 'schema' 44 | ]; 45 | 46 | keys.forEach(key => { 47 | if (tempConfig[key]) { 48 | tempConfig[key] = tempConfig[key]; 49 | } 50 | }); 51 | 52 | _mongodb = auth(tempConfig, options); 53 | } 54 | 55 | /** 56 | * Get the MongoDB configuration 57 | * 58 | * @returns {MongoConfig} 59 | * @throws {MongoRuntimeError} if MongoDB is not initialized 60 | */ 61 | export function getMongoConfig(): MongoConfig { 62 | if (!_mongoConfig) { 63 | throw new MongoRuntimeError('MongoDB is not initialized'); 64 | } 65 | return _mongoConfig; 66 | } 67 | 68 | /** 69 | * Get the MongoDB client 70 | * 71 | * @returns {MongoClient} 72 | * @throws {MongoRuntimeError} if MongoDB is not initialized 73 | */ 74 | export function getMongoClient(): MongoClient { 75 | if (!_mongodb) { 76 | throw new MongoRuntimeError('MongoDB is not initialized'); 77 | } 78 | return _mongodb; 79 | } 80 | 81 | /** 82 | * Checks if the collection exists in the database 83 | * 84 | * @example 85 | * 86 | * import { MongoDB } from "@litehex/mongodb"; 87 | * 88 | * const exists = await MongoDB.collectionExists('litehex', 'users'); 89 | * 90 | * @param {Db} dbInstance 91 | * @param {string} collection 92 | * 93 | * @returns {Promise} 94 | */ 95 | async function collectionExists(dbInstance: Db, collection: string): Promise { 96 | 97 | const collections = await dbInstance.listCollections().toArray(); 98 | 99 | return collections.some(({ name }) => { 100 | return name === collection; 101 | }); 102 | } 103 | 104 | /** 105 | * Checks if the database exists in the database 106 | * 107 | * 108 | * @example 109 | * 110 | * import { MongoDB } from "@litehex/mongodb"; 111 | * 112 | * const exists = await MongoDB.databaseExists('litehex'); 113 | * 114 | * @param {string} dbName 115 | * @param {MongoClient?} clientInstance 116 | * 117 | * @returns {Promise} 118 | */ 119 | async function databaseExists(dbName: string, clientInstance?: MongoClient): Promise { 120 | 121 | const client = clientInstance || _mongodb; 122 | 123 | if (!client) { 124 | throw new MongoError('Client has not been initialized'); 125 | } 126 | 127 | const databases = await client.db().admin().listDatabases(); 128 | 129 | return databases.databases.some(({ name }) => { 130 | return name === dbName; 131 | }); 132 | } 133 | 134 | /** 135 | * Checks if the database is connected 136 | * 137 | * @param {Db} db 138 | * @returns {Promise} 139 | */ 140 | async function isConnected(db: Db): Promise { 141 | try { 142 | await db.command({ ping: 1 }); 143 | return true; 144 | } catch (e) { 145 | return false; 146 | } 147 | } 148 | 149 | async function connect(database?: string): Promise { 150 | if (!_mongodb) { 151 | throw new MongoError('MongoClient has not been initialized'); 152 | } 153 | await _mongodb.connect(); 154 | return _mongodb.db(database); 155 | } 156 | 157 | /** 158 | * Disconnect client from MongoDB cluster 159 | * 160 | * @param {boolean} force - Closes every connection in the connection pool 161 | * @returns {Promise} 162 | */ 163 | async function disconnect(force: boolean = false): Promise { 164 | if (force) { 165 | await Promise.all(Object.values(_instances).map(client => client.close())); 166 | return; 167 | } 168 | 169 | if (_mongodb) { 170 | await _mongodb.close(); 171 | } 172 | } 173 | 174 | function db(database: string): Db { 175 | if (!_mongodb) { 176 | throw new MongoError('MongoClient has not been initialized'); 177 | } 178 | const db = _mongodb.db(database); 179 | !isConnected(db) && connect(database); 180 | return db; 181 | } 182 | 183 | /** 184 | * Create a MongoClient instance 185 | * 186 | * @param {MongoConfig} config 187 | * @param {MongoClientOptions} options 188 | * 189 | * @returns {MongoClient} 190 | */ 191 | function auth(config: MongoConfig, options?: MongoClientOptions): MongoClient { 192 | 193 | const url = makeConnectionString(config); 194 | const instants = new MongoClient(url, options); 195 | 196 | if (!_instances[url]) { 197 | _instances[url] = instants; 198 | } 199 | 200 | return _instances[url]; 201 | } 202 | 203 | /** 204 | * Rename a Database 205 | * 206 | * @param {string} dbName 207 | * @param {string} newDbName 208 | * 209 | * @returns {Promise} 210 | */ 211 | async function renameDatabase(dbName: string, newDbName: string): Promise { 212 | if (!_mongodb) { 213 | throw new MongoError('MongoClient has not been initialized'); 214 | } 215 | 216 | const db = _mongodb.db(dbName); 217 | const newDb = _mongodb.db(newDbName); 218 | 219 | if (await databaseExists(newDbName)) { 220 | throw new MongoRuntimeError(`Database "${newDbName}" already exists`); 221 | } 222 | 223 | if (!await databaseExists(dbName)) { 224 | throw new MongoRuntimeError(`Database "${dbName}" doesn't exist`); 225 | } 226 | 227 | if (dbName === newDbName) { 228 | throw new MongoRuntimeError(`Another database with the same name "${dbName}" already exists`); 229 | } 230 | 231 | const collections = await db.listCollections().toArray(); 232 | 233 | for (const { name } of collections) { 234 | const docs = await db.collection(name).find().toArray(); 235 | await newDb.createCollection(name); 236 | 237 | if (docs.length > 0) { 238 | await newDb.collection(name).insertMany(docs); 239 | } 240 | 241 | await db.collection(name).drop(); 242 | } 243 | 244 | await db.dropDatabase(); 245 | 246 | return !!newDb 247 | } 248 | 249 | export const MongoDB = { 250 | auth, 251 | connect, 252 | disconnect, 253 | isConnected, 254 | db, 255 | renameDatabase, 256 | collectionExists, 257 | getClient: getMongoClient, 258 | getConfig: getMongoConfig, 259 | utils: Utils 260 | } 261 | 262 | /** 263 | * Exporting the Collection module 264 | */ 265 | export * from './collection'; 266 | 267 | /** 268 | * Exporting the Utils module 269 | */ 270 | export * from './utils'; 271 | 272 | /** 273 | * Exporting the Original MongoDB types 274 | */ 275 | export * from './types'; 276 | 277 | /** 278 | * Export default MongoDB module 279 | */ 280 | export default MongoDB; 281 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BulkWriteOptions, 3 | Collection, 4 | CollectionOptions, 5 | Condition, 6 | Db, 7 | DeleteOptions, 8 | DeleteResult, 9 | Document, 10 | FindCursor as MongoCursor, 11 | FindOptions, 12 | InsertManyResult, 13 | InsertOneOptions, 14 | InsertOneResult, 15 | Join, 16 | MongoClientOptions, 17 | MongoError, 18 | NestedPaths, 19 | ObjectId, 20 | PropertyType, 21 | RootFilterOperators, 22 | UpdateFilter, 23 | UpdateResult as MongoUpdateResult 24 | } from 'mongodb'; 25 | 26 | export type { 27 | Collection as MongoCollection, 28 | BulkWriteOptions, 29 | Collection, 30 | CollectionOptions, 31 | Db, 32 | DeleteOptions, 33 | DeleteResult, 34 | Document, 35 | FindOptions, 36 | InsertManyResult, 37 | InsertOneOptions, 38 | InsertOneResult, 39 | MongoError, 40 | ObjectId 41 | } from "mongodb"; 42 | 43 | export interface MongoConfig { 44 | hostname: string 45 | schema?: string 46 | port?: string | number 47 | params?: Record & EnchantedOptions 48 | username?: string 49 | password?: string 50 | database?: string 51 | } 52 | 53 | export interface EnchantedOptions extends Omit { 54 | proxyPort?: number | string 55 | } 56 | 57 | export interface ObjectOptions { 58 | $unset?: string[], 59 | $set?: Record, 60 | $rename?: Record 61 | } 62 | 63 | export type UpdateQuery = UpdateFilter | Partial; 64 | 65 | export type UpdateResult = TSchema | MongoUpdateResult; 66 | 67 | export type LeastOne }> = Partial & U[keyof U]; 68 | 69 | export type Filter = Partial | ({ 70 | [Property in Join, []>, '.'>]?: Condition, Property>>; 71 | } & RootFilterOperators>) | ({ 72 | [Property in Join, '.'>]?: Condition>; 73 | } & RootFilterOperators) 74 | 75 | export type WithId = TSchema & { _id: ObjectId } 76 | 77 | export type OptionalId = Omit & { _id?: ObjectId | string } 78 | 79 | export type FindCursor = MongoCursor | MongoCursor> 80 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { MongoConfig, ObjectOptions } from "./types"; 2 | 3 | export function toObject(doc: any, exec: ObjectOptions = {}) { 4 | 5 | if (!doc) { 6 | return doc; 7 | } 8 | 9 | const { $unset, $set, $rename } = exec; 10 | 11 | $unset && $unset.forEach(key => { 12 | if (Object.keys(doc).includes(key)) { 13 | delete doc[key]; 14 | } else { 15 | if (key.includes('.')) { 16 | const keys = key.split('.'); 17 | keys.reduce((acc, cur, index) => { 18 | if (index === keys.length - 1) { 19 | if (acc && Object.keys(acc).includes(cur)) { 20 | delete acc[cur]; 21 | } else { 22 | return acc; 23 | } 24 | } 25 | return acc[cur]; 26 | }, doc); 27 | } 28 | } 29 | }); 30 | 31 | $set && Object.assign(doc, $set); 32 | 33 | $rename && Object.keys($rename).forEach(key => { 34 | if (Object.keys(doc).includes(key)) { 35 | doc[$rename[key]] = doc[key]; 36 | delete doc[key]; 37 | } else { 38 | if (key.includes('.')) { 39 | const keys = key.split('.'); 40 | keys.reduce((acc, cur, index) => { 41 | if (index === keys.length - 1) { 42 | doc[$rename[key]] = acc[cur]; 43 | delete acc[cur]; 44 | } 45 | return acc[cur]; 46 | }, doc); 47 | } 48 | } 49 | }); 50 | 51 | return JSON.parse(JSON.stringify(doc)); 52 | } 53 | 54 | export function makeConnectionString(config: MongoConfig): string { 55 | 56 | const { username, password, hostname, port, database } = config; 57 | 58 | const portStr = config.schema === 'mongodb+srv' ? '' : `:${port?.toString() || '27017'}`; 59 | 60 | const schema = config.schema || 'mongodb'; 61 | let paramsString = '' 62 | 63 | if (config.params) { 64 | paramsString += '/?'; 65 | Object.keys(config.params).forEach(key => { 66 | paramsString += `${key}=${config.params ? config.params[key] : ''}&`; 67 | }); 68 | paramsString = paramsString.slice(0, -1); 69 | } 70 | 71 | let base = `${schema}://${hostname}${portStr}${paramsString}`; 72 | if (username && password) { 73 | base = `${schema}://${username}:${password}@${hostname}${portStr}${paramsString}`; 74 | } 75 | 76 | return database ? `${base}/${database}` : base; 77 | } 78 | -------------------------------------------------------------------------------- /tests/mongodb.ts: -------------------------------------------------------------------------------- 1 | import MongoCollection, { CollectionConfig } from "../src/collection"; 2 | import MongoDB, { InitMongo, OptionalId } from "../src/index"; 3 | 4 | export { MongoDB } from "../src/index"; 5 | 6 | InitMongo({ 7 | hostname: process.env.MONGO_HOSTNAME || "localhost", 8 | port: process.env.MONGO_PORT || 27017, 9 | username: process.env.MONGO_USERNAME, 10 | password: process.env.MONGO_PASSWORD, 11 | params: { 12 | authMechanism: "DEFAULT" 13 | } 14 | }); 15 | 16 | export const DATABASE = { 17 | TEST: "test" 18 | }; 19 | 20 | export const db = MongoDB.db(DATABASE.TEST); 21 | 22 | export type Todo = OptionalId<{ 23 | title: string; 24 | completed: boolean; 25 | createdAt: Date; 26 | }> 27 | 28 | export class TodoCollection extends MongoCollection { 29 | getConfig(): CollectionConfig { 30 | return { 31 | name: "todo", 32 | database: DATABASE.TEST, 33 | timestamps: true, 34 | timestampsFields: { 35 | createdAt: "createdAt" 36 | } 37 | }; 38 | } 39 | 40 | static createOne({ name, completed }: { name: string, completed: boolean }) { 41 | return this.insertOne({ name, completed }); 42 | } 43 | } 44 | 45 | export type User = OptionalId<{ 46 | nickname: string; 47 | email: string; 48 | gems: number; 49 | }> 50 | 51 | export class UsersCollection extends MongoCollection { 52 | getConfig(): CollectionConfig { 53 | return { 54 | name: "users", 55 | database: DATABASE.TEST, 56 | timestamps: true 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/simple.test.ts: -------------------------------------------------------------------------------- 1 | import { afterAll, describe, expect, test } from "@jest/globals"; 2 | import { db, MongoDB, Todo, TodoCollection, User, UsersCollection } from "./mongodb"; 3 | import { faker } from "@faker-js/faker"; 4 | import { green as logG } from "chalk"; 5 | 6 | const tenMin = 1000 * 60 * 10; 7 | 8 | describe("MongoDB", () => { 9 | 10 | test("should connect to MongoDB", async () => { 11 | expect(db).toBeTruthy(); 12 | }, tenMin); 13 | 14 | test("Database exists", async () => { 15 | const collections = await db.listCollections().toArray(); 16 | expect(collections.length).toBeGreaterThanOrEqual(0); 17 | }, tenMin); 18 | 19 | }); 20 | 21 | describe("Collection", () => { 22 | 23 | test("Get collection instance", async () => { 24 | expect(TodoCollection.getCollection()).toBeTruthy(); 25 | }, tenMin); 26 | 27 | test("Insert document", async () => { 28 | const result = await TodoCollection.createOne({ 29 | name: faker.lorem.sentence(), 30 | completed: faker.datatype.boolean() 31 | }); 32 | expect(result.acknowledged).toBeTruthy(); 33 | }, tenMin); 34 | 35 | test("Get document", async () => { 36 | const result = await TodoCollection.findOne({}); 37 | expect(result).toBeTruthy(); 38 | }, tenMin); 39 | 40 | }); 41 | 42 | describe("Generic Collections and Models", () => { 43 | 44 | test("Get Gems of a User", async () => { 45 | 46 | const result = await UsersCollection.findOne({}); 47 | 48 | expect(result).toBeTruthy(); 49 | expect(result?.gems).toBeGreaterThanOrEqual(0); 50 | 51 | }, tenMin); 52 | 53 | test("Made up a Dummy User", async () => { 54 | 55 | const user: User = { 56 | nickname: faker.name.firstName(), 57 | email: faker.internet.email(), 58 | gems: faker.datatype.number() 59 | } 60 | 61 | const result = await UsersCollection.insertOne(user); 62 | 63 | expect(result.acknowledged).toBeTruthy(); 64 | 65 | }, tenMin); 66 | 67 | test("Update a User", async () => { 68 | 69 | const user = await UsersCollection.findOne({}); 70 | 71 | expect(user).toBeTruthy(); 72 | 73 | const result = await UsersCollection.updateOne({_id: user?._id}, { 74 | $set: { 75 | gems: faker.datatype.number() 76 | } 77 | }); 78 | 79 | expect(result.acknowledged).toBeTruthy(); 80 | 81 | }, tenMin); 82 | 83 | test("Modify a document", async () => { 84 | const doc = await TodoCollection.getCollection().findOne({ 85 | completed: false 86 | }); 87 | expect(doc).toBeTruthy(); 88 | const newDoc = MongoDB.utils.toObject(doc, { 89 | $unset: ["name"] 90 | }); 91 | expect(newDoc).toBeTruthy(); 92 | expect(newDoc.name).toBeUndefined(); 93 | }, tenMin); 94 | 95 | }); 96 | 97 | describe("Mongo Url Maker", () => { 98 | 99 | 100 | const randomHostname = faker.internet.domainName(); 101 | 102 | test("A SRV Schema", async () => { 103 | 104 | expect(MongoDB.utils.makeConnectionString({ 105 | hostname: randomHostname, 106 | schema: "mongodb+srv", 107 | username: "root", 108 | password: "password" 109 | })).toBe(`mongodb+srv://root:password@${randomHostname}`); 110 | 111 | }); 112 | 113 | test("A Normal Schema with Params", async () => { 114 | expect(MongoDB.utils.makeConnectionString({ 115 | hostname: "localhost", 116 | port: 27017, 117 | username: "root", 118 | password: "password", 119 | params: { 120 | authMechanism: "DEFAULT" 121 | } 122 | })).toBe("mongodb://root:password@localhost:27017/?authMechanism=DEFAULT"); 123 | }); 124 | 125 | test("With Proxy settings", async () => { 126 | expect(MongoDB.utils.makeConnectionString({ 127 | hostname: "localhost", 128 | username: "root", 129 | password: "password", 130 | params: { 131 | proxyHost: randomHostname, 132 | proxyPort: 1080 133 | } 134 | })).toBe(`mongodb://root:password@localhost:27017/?proxyHost=${randomHostname}&proxyPort=1080`); 135 | }); 136 | 137 | }); 138 | 139 | afterAll(async () => { 140 | await MongoDB.disconnect(); 141 | console.info(logG("Disconnected from MongoDB")); 142 | }); 143 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es5", 5 | "outDir": "dist", 6 | "sourceMap": true, 7 | "declaration": true, 8 | "skipLibCheck": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "esModuleInterop": true, 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true 14 | }, 15 | "ts-node": { 16 | "compilerOptions": { 17 | "module": "commonjs" 18 | } 19 | }, 20 | "include": ["**/*.ts"], 21 | "exclude": ["node_modules", "dist", "tests"] 22 | } 23 | --------------------------------------------------------------------------------