├── .codeclimate.yml ├── .editorconfig ├── .eslintrc.js ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .vscode ├── launch.json └── settings.json ├── LICENSE ├── README.md ├── benchmark ├── .eslintrc.js ├── index.js └── suites │ ├── .gitignore │ └── common.js ├── dev.js ├── lerna.json ├── package-lock.json ├── package.json ├── packages ├── moleculer-db-adapter-couchdb-nano │ ├── .gitignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── examples │ │ ├── index.js │ │ ├── integration │ │ │ └── index.js │ │ └── simple │ │ │ └── index.js │ ├── index.js │ ├── package-lock.json │ ├── package.json │ ├── src │ │ └── index.js │ └── test │ │ └── unit │ │ └── index.spec.js ├── moleculer-db-adapter-mongo │ ├── .gitignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── examples │ │ ├── index.js │ │ ├── integration │ │ │ └── index.js │ │ ├── search │ │ │ └── index.js │ │ └── simple │ │ │ └── index.js │ ├── index.d.ts │ ├── index.js │ ├── package-lock.json │ ├── package.json │ ├── src │ │ └── index.js │ └── test │ │ └── unit │ │ └── index.spec.js ├── moleculer-db-adapter-mongoose │ ├── .eslintrc.js │ ├── .gitignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── examples │ │ ├── index.js │ │ ├── integration │ │ │ └── index.js │ │ ├── models │ │ │ ├── posts.js │ │ │ └── users.js │ │ ├── populates │ │ │ └── index.js │ │ ├── search │ │ │ └── index.js │ │ └── simple │ │ │ └── index.js │ ├── index.d.ts │ ├── index.js │ ├── package-lock.json │ ├── package.json │ ├── src │ │ └── index.js │ └── test │ │ ├── integration │ │ └── virtuals.spec.js │ │ ├── models │ │ ├── posts.js │ │ └── users.js │ │ └── unit │ │ └── index.spec.js ├── moleculer-db-adapter-sequelize │ ├── .gitignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── examples │ │ ├── index.js │ │ ├── integration │ │ │ └── index.js │ │ └── simple │ │ │ └── index.js │ ├── index.d.ts │ ├── index.js │ ├── package-lock.json │ ├── package.json │ ├── src │ │ └── index.js │ └── test │ │ └── unit │ │ └── index.spec.js └── moleculer-db │ ├── .gitignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── examples │ ├── encode │ │ └── index.js │ ├── index.js │ ├── pagination │ │ └── index.js │ ├── populates │ │ ├── index.js │ │ ├── posts.db │ │ └── users.db │ └── simple │ │ └── index.js │ ├── index.js │ ├── moleculer-db.d.ts │ ├── package-lock.json │ ├── package.json │ ├── src │ ├── errors.js │ ├── index.js │ ├── memory-adapter.js │ └── utils.js │ └── test │ ├── checker.js │ ├── integration │ ├── crud.spec.js │ ├── idField.test.js │ └── populates.spec.js │ └── unit │ ├── errors.spec.js │ ├── index.actions.spec.js │ ├── index.methods.spec.js │ ├── index.spec.js │ ├── memory-adapter.spec.js │ └── utils.spec.js ├── proposal └── moleculer-db-adapter-knex │ ├── .gitignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── examples │ ├── index.js │ └── simple │ │ └── index.js │ ├── index.js │ ├── package.json │ ├── src │ └── index.js │ └── test │ └── unit │ └── index.spec.js └── readme-generator.js /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | 3 | checks: 4 | argument-count: 5 | enabled: false 6 | complex-logic: 7 | enabled: false 8 | file-lines: 9 | enabled: false 10 | method-complexity: 11 | enabled: false 12 | method-count: 13 | enabled: false 14 | method-lines: 15 | enabled: false 16 | nested-control-flow: 17 | enabled: false 18 | return-statements: 19 | enabled: false 20 | similar-code: 21 | enabled: false 22 | identical-code: 23 | enabled: false 24 | 25 | plugins: 26 | duplication: 27 | enabled: false 28 | config: 29 | languages: 30 | - javascript 31 | eslint: 32 | enabled: true 33 | channel: "eslint-4" 34 | fixme: 35 | enabled: true 36 | 37 | exclude_paths: 38 | - test/ 39 | - benchmark/ 40 | - examples/ 41 | - typings/ 42 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | 9 | # Change these settings to your own preference 10 | indent_style = tab 11 | indent_size = 4 12 | space_after_anon_function = true 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | indent_style = space 22 | indent_size = 4 23 | trim_trailing_whitespace = false 24 | 25 | [{package,bower}.json] 26 | indent_style = space 27 | indent_size = 2 28 | 29 | [*.js] 30 | quote_type = "double" 31 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | "env": { 4 | "node": true, 5 | "commonjs": true, 6 | "es6": true, 7 | "jquery": false, 8 | "jest": true, 9 | "jasmine": true 10 | }, 11 | "extends": "eslint:recommended", 12 | "parserOptions": { 13 | "sourceType": "module", 14 | "ecmaVersion": "2018" 15 | }, 16 | "rules": { 17 | "indent": [ 18 | "warn", 19 | "tab", 20 | { SwitchCase: 1 } 21 | ], 22 | "quotes": [ 23 | "warn", 24 | "double" 25 | ], 26 | "semi": [ 27 | "error", 28 | "always" 29 | ], 30 | "no-var": [ 31 | "error" 32 | ], 33 | "no-console": [ 34 | "off" 35 | ], 36 | "no-unused-vars": [ 37 | "warn" 38 | ] 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI test 2 | 3 | on: 4 | push: {} 5 | pull_request: {} 6 | 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | # node-version: [10.x, 12.x, 14.x, 15.x] 13 | node-version: [10.x, 12.x, 14.x, 16.x, 18.x, 20.x] 14 | fail-fast: false 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - name: Use Node.js ${{ matrix.node-version }} 20 | uses: actions/setup-node@v4 21 | with: 22 | node-version: ${{ matrix.node-version }} 23 | 24 | - name: Cache node modules 25 | uses: actions/cache@v4 26 | env: 27 | cache-name: cache-node-modules 28 | with: 29 | # npm cache files are stored in `~/.npm` on Linux/macOS 30 | path: ~/.npm 31 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} 32 | restore-keys: | 33 | ${{ runner.os }}-build-${{ env.cache-name }}- 34 | ${{ runner.os }}-build- 35 | ${{ runner.os }}- 36 | 37 | - name: Install dependencies 38 | run: npm run setup-no-old-node 39 | if: ${{ matrix.node-version == '10.x' || matrix.node-version == '12.x' }} 40 | 41 | - name: Install dependencies 42 | run: npm run setup 43 | if: ${{ matrix.node-version != '10.x' && matrix.node-version != '12.x' }} 44 | 45 | - name: Execute unit tests 46 | run: npm run test:unit 47 | 48 | - name: Start MongoDB 49 | uses: supercharge/mongodb-github-action@1.9.0 50 | with: 51 | mongodb-version: 4.4 52 | 53 | - name: Execute integration tests 54 | run: npm run test:integration 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # WebStorm settings 2 | .idea 3 | 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | lerna-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 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 | 24 | # nyc test coverage 25 | .nyc_output 26 | 27 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 28 | .grunt 29 | 30 | # Bower dependency directory (https://bower.io/) 31 | bower_components 32 | 33 | # node-waf configuration 34 | .lock-wscript 35 | 36 | # Compiled binary addons (http://nodejs.org/api/addons.html) 37 | build/Release 38 | 39 | # Dependency directories 40 | node_modules/ 41 | jspm_packages/ 42 | 43 | # Typescript v1 declaration files 44 | typings/ 45 | 46 | # Optional npm cache directory 47 | .npm 48 | 49 | # Optional eslint cache 50 | .eslintcache 51 | 52 | # Optional REPL history 53 | .node_repl_history 54 | 55 | # Output of 'npm pack' 56 | *.tgz 57 | 58 | # Yarn Integrity file 59 | .yarn-integrity 60 | 61 | # dotenv environment variables file 62 | .env 63 | 64 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible Node.js debug attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch dev", 11 | "program": "${workspaceRoot}/dev", 12 | "cwd": "${workspaceRoot}", 13 | "args": [ 14 | "moleculer-db-adapter-mongoose", 15 | "simple" 16 | ] 17 | }, 18 | { 19 | "type": "node", 20 | "request": "launch", 21 | "name": "Launch readme gen", 22 | "program": "${workspaceRoot}/node_modules/moleculer-docgen/index.js", 23 | "args": [ 24 | "-t", 25 | "packages/moleculer-db/README.test.md", 26 | "packages/moleculer-db/src/index.js" 27 | ], 28 | "stopOnEntry": true 29 | 30 | }, 31 | { 32 | "type": "node", 33 | "request": "launch", 34 | "name": "Launch benchmark", 35 | "program": "${workspaceRoot}/benchmark/index.js", 36 | "cwd": "${workspaceRoot}", 37 | "args": [ 38 | "common" 39 | ] 40 | }, 41 | { 42 | "name": "Attach by Benchmark", 43 | "processId": "${command:PickProcess}", 44 | "request": "attach", 45 | "skipFiles": [ 46 | "/**" 47 | ], 48 | "type": "node" 49 | }, 50 | { 51 | "type": "node", 52 | "request": "launch", 53 | "name": "Launch readme", 54 | "program": "${workspaceRoot}/readme-generator", 55 | "cwd": "${workspaceRoot}" 56 | }, 57 | { 58 | "type": "node", 59 | "request": "launch", 60 | "name": "Jest", 61 | "program": "${workspaceRoot}/node_modules/jest-cli/bin/jest.js", 62 | "args": ["--runInBand"], 63 | "cwd": "${workspaceRoot}", 64 | "runtimeArgs": [ 65 | "--nolazy" 66 | ] 67 | } 68 | ] 69 | } 70 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "standard.enable": false 3 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 MoleculerJS 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 | ![Moleculer logo](https://moleculer.services/images/banner.png) 2 | 3 | ![CI test](https://github.com/moleculerjs/moleculer-db/workflows/CI%20test/badge.svg) 4 | [![Build Status](https://travis-ci.org/moleculerjs/moleculer-db.svg?branch=master)](https://travis-ci.org/moleculerjs/moleculer-db) 5 | [![Coverage Status](https://coveralls.io/repos/github/moleculerjs/moleculer-db/badge.svg?branch=master)](https://coveralls.io/github/moleculerjs/moleculer-db?branch=master) 6 | [![Maintainability](https://api.codeclimate.com/v1/badges/0c7fa55afd189410eff3/maintainability)](https://codeclimate.com/github/moleculerjs/moleculer-db/maintainability) 7 | [![Known Vulnerabilities](https://snyk.io/test/github/moleculerjs/moleculer-db/badge.svg)](https://snyk.io/test/github/moleculerjs/moleculer-db) 8 | [![Discord chat](https://img.shields.io/discord/585148559155003392)](https://discord.gg/TSEcDRP) 9 | 10 | # Official DB addons for Moleculer framework 11 | 12 | 13 | ## Generals 14 | | Name | Version | Description | 15 | | ---- | ------- | ----------- | 16 | | [moleculer-db](/packages/moleculer-db#readme) | [![NPM version](https://img.shields.io/npm/v/moleculer-db.svg)](https://www.npmjs.com/package/moleculer-db) | Moleculer service to store entities in database | 17 | | [moleculer-db-adapter-couchdb-nano](/packages/moleculer-db-adapter-couchdb-nano#readme) | [![NPM version](https://img.shields.io/npm/v/moleculer-db-adapter-couchdb-nano.svg)](https://www.npmjs.com/package/moleculer-db-adapter-couchdb-nano) | CouchDB Nano adapter for Moleculer DB service. | 18 | | [moleculer-db-adapter-mongo](/packages/moleculer-db-adapter-mongo#readme) | [![NPM version](https://img.shields.io/npm/v/moleculer-db-adapter-mongo.svg)](https://www.npmjs.com/package/moleculer-db-adapter-mongo) | MongoDB native adapter for Moleculer DB service. | 19 | | [moleculer-db-adapter-mongoose](/packages/moleculer-db-adapter-mongoose#readme) | [![NPM version](https://img.shields.io/npm/v/moleculer-db-adapter-mongoose.svg)](https://www.npmjs.com/package/moleculer-db-adapter-mongoose) | Mongoose adapter for Moleculer DB service | 20 | | [moleculer-db-adapter-sequelize](/packages/moleculer-db-adapter-sequelize#readme) | [![NPM version](https://img.shields.io/npm/v/moleculer-db-adapter-sequelize.svg)](https://www.npmjs.com/package/moleculer-db-adapter-sequelize) | SQL adapter (Postgres, MySQL, SQLite & MSSQL) for Moleculer DB service | 21 | 22 | 23 | # Contribution 24 | 25 | ## Install dependencies 26 | ```bash 27 | $ npm run setup 28 | ``` 29 | 30 | ## Development 31 | **Run the `simple` example in `moleculer-db` service with watching** 32 | ```bash 33 | $ npm run dev moleculer-db 34 | ``` 35 | 36 | **Run the `full` example in `moleculer-db` service w/o watching** 37 | ```bash 38 | $ npm run demo moleculer-db full 39 | ``` 40 | 41 | ## Test 42 | ```bash 43 | $ npm test 44 | ``` 45 | 46 | ## Create a new addon 47 | ```bash 48 | $ npm run init moleculer- 49 | ``` 50 | 51 | ## Publish new releases 52 | ```bash 53 | $ npm run release 54 | ``` 55 | 56 | # License 57 | The project is available under the [MIT license](https://tldrlegal.com/license/mit-license). 58 | 59 | # Contact 60 | Copyright (c) 2016-2024 MoleculerJS 61 | 62 | [![@moleculerjs](https://img.shields.io/badge/github-moleculerjs-green.svg)](https://github.com/moleculerjs) [![@MoleculerJS](https://img.shields.io/badge/twitter-MoleculerJS-blue.svg)](https://twitter.com/MoleculerJS) 63 | -------------------------------------------------------------------------------- /benchmark/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | node: true, 4 | commonjs: true, 5 | es6: true 6 | }, 7 | extends: ["eslint:recommended"], 8 | parserOptions: { 9 | sourceType: "module", 10 | ecmaVersion: 2017, 11 | ecmaFeatures: { 12 | experimentalObjectRestSpread: true 13 | } 14 | }, 15 | rules: { 16 | indent: ["warn", "tab", { SwitchCase: 1 }], 17 | quotes: ["warn", "double"], 18 | semi: ["error", "always"], 19 | "no-var": ["warn"], 20 | "no-console": ["off"], 21 | "no-unused-vars": ["off"], 22 | "no-trailing-spaces": ["error"], 23 | "security/detect-possible-timing-attacks": ["off"] 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /benchmark/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | require("./suites/" + (process.argv[2] || "common")); -------------------------------------------------------------------------------- /benchmark/suites/.gitignore: -------------------------------------------------------------------------------- 1 | tmp/ 2 | -------------------------------------------------------------------------------- /benchmark/suites/common.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const path = require("path"); 4 | const fs = require("fs"); 5 | const { makeDirs } = require("moleculer").Utils; 6 | 7 | const Benchmarkify = require("benchmarkify"); 8 | const _ = require("lodash"); 9 | const { ServiceBroker } = require("moleculer"); 10 | const DbService = require("../../packages/moleculer-db/index"); 11 | const SequelizeAdapter = require("../../packages/moleculer-db-adapter-sequelize/index"); 12 | const Sequelize = require("sequelize"); 13 | 14 | const Fakerator = require("fakerator"); 15 | const fakerator = new Fakerator(); 16 | 17 | const COUNT = 1000; 18 | 19 | makeDirs(path.join(__dirname, "tmp")); 20 | 21 | const neDBFileName = path.join(__dirname, "tmp", "common.db"); 22 | const sqliteFilename = path.join(__dirname, "tmp", "common.sqlite3"); 23 | 24 | const Adapters = [ 25 | //{ name: "NeDB (memory)", type: "NeDB", adapter: new DbService.MemoryAdapter(), ref: true }, 26 | { name: "NeDB (file)", type: "NeDB", adapter: new DbService.MemoryAdapter({ filename: neDBFileName }) }, 27 | { name: "SQLite (memory)", type: "Sequelize", adapter: new SequelizeAdapter("sqlite://:memory:", { logging: false }) }, 28 | // { name: "SQLite (file)", type: "Sequelize", adapter: new SequelizeAdapter("sqlite://benchmark/suites/tmp/common.sqlite3", { logging: false }) }, 29 | ]; 30 | 31 | const benchmark = new Benchmarkify("Moleculer Database benchmark - Common"); 32 | 33 | const suites = []; 34 | 35 | const UserServiceSchema = (serviceName, adapterDef) => { 36 | return { 37 | name: serviceName, 38 | mixins: [DbService], 39 | adapter: adapterDef.adapter, 40 | model: { 41 | name: "post", 42 | define: { 43 | firstName: Sequelize.STRING, 44 | lastName: Sequelize.STRING, 45 | userName: Sequelize.STRING, 46 | email: Sequelize.STRING, 47 | password: Sequelize.STRING, 48 | status: Sequelize.BOOLEAN 49 | }, 50 | options: { 51 | 52 | } 53 | }, 54 | settings: { 55 | idField: "id", 56 | fields: ["id", "firstName", "lastName", "userName", "email", "password", "status"], 57 | }, 58 | async started() { 59 | await this.adapter.clear(); 60 | } 61 | }; 62 | }; 63 | 64 | const USERS = fakerator.times(fakerator.entity.user, COUNT * 5).map(user => { 65 | return _.pick(user, ["firstName", "lastName", "userName", "email", "password", "status"]); 66 | }); 67 | 68 | const broker = new ServiceBroker({ logger: false }); 69 | Adapters.forEach((adapterDef, i) => { 70 | // const adapterName = adapterDef.name || adapterDef.type; 71 | const serviceName = `users-${i}`; 72 | adapterDef.svcName = serviceName; 73 | adapterDef.svc = broker.createService(UserServiceSchema(serviceName, adapterDef)); 74 | }); 75 | 76 | // --- ENTITY CREATION --- 77 | (function () { 78 | const bench = benchmark.createSuite("Entity creation", { 79 | description: "This test calls the `create` action to create an entity.", 80 | meta: { 81 | type: "create" 82 | } 83 | }); 84 | suites.push(bench); 85 | const tearDowns = []; 86 | bench.tearDown(tearDowns); 87 | 88 | Adapters.forEach(adapterDef => { 89 | const adapterName = adapterDef.name || adapterDef.type; 90 | const svc = adapterDef.svc; 91 | const actionName = `${adapterDef.svcName}.create`; 92 | 93 | const len = USERS.length; 94 | let c = 0; 95 | bench[adapterDef.ref ? "ref" : "add"](adapterName, done => { 96 | broker.call(actionName, USERS[c++ % len]).then(done).catch(err => console.error(err)); 97 | }); 98 | 99 | // Clear all entities and create only the specified count. 100 | tearDowns.push(async () => { 101 | try { 102 | await svc.adapter.clear(); 103 | await svc.adapter.insertMany(USERS.slice(0, COUNT)); 104 | } catch(err) { 105 | console.error(err); 106 | } 107 | }); 108 | }); 109 | })(); 110 | 111 | // --- ENTITY FINDING --- 112 | (function () { 113 | const bench = benchmark.createSuite("Entity finding", { 114 | description: "This test calls the `find` action to get random 20 entities.", 115 | meta: { 116 | type: "find" 117 | } 118 | }); 119 | suites.push(bench); 120 | 121 | Adapters.forEach(adapterDef => { 122 | const adapterName = adapterDef.name || adapterDef.type; 123 | const actionName = `${adapterDef.svcName}.find`; 124 | 125 | let c = 0; 126 | bench[adapterDef.ref ? "ref" : "add"](adapterName, done => { 127 | const offset = Math.floor(Math.random() * (COUNT - 20)); 128 | broker.call(actionName, { offset, limit: 20 }).then(done).catch(err => console.error(err)); 129 | }); 130 | }); 131 | })(); 132 | 133 | // --- ENTITY LISTING --- 134 | (function () { 135 | const bench = benchmark.createSuite("Entity listing", { 136 | description: "This test calls the `users.list` service action to random 20 entities.", 137 | meta: { 138 | type: "list" 139 | } 140 | }); 141 | suites.push(bench); 142 | 143 | Adapters.forEach(adapterDef => { 144 | const adapterName = adapterDef.name || adapterDef.type; 145 | const actionName = `${adapterDef.svcName}.list`; 146 | 147 | const maxPage = COUNT / 20 - 2; 148 | let c = 0; 149 | bench[adapterDef.ref ? "ref" : "add"](adapterName, done => { 150 | const page = Math.floor(Math.random() * maxPage) + 1; 151 | broker.call(actionName, { page, pageSize: 20 }).then(done).catch(err => console.error(err)); 152 | }); 153 | }); 154 | })(); 155 | 156 | // --- ENTITY COUNTING --- 157 | (function () { 158 | const bench = benchmark.createSuite("Entity counting", { 159 | description: 160 | "This test calls the `users.count` service action to get the number of entities.", 161 | meta: { 162 | type: "count" 163 | } 164 | }); 165 | suites.push(bench); 166 | 167 | Adapters.forEach(adapterDef => { 168 | const adapterName = adapterDef.name || adapterDef.type; 169 | const actionName = `${adapterDef.svcName}.count`; 170 | 171 | let c = 0; 172 | bench[adapterDef.ref ? "ref" : "add"](adapterName, done => { 173 | broker.call(actionName).then(done).catch(err => console.error(err)); 174 | }); 175 | }); 176 | })(); 177 | 178 | // --- ENTITY GETTING --- 179 | (function () { 180 | const bench = benchmark.createSuite("Entity getting", { 181 | description: "This test calls the `users.get` service action to get a random entity.", 182 | meta: { 183 | type: "get" 184 | } 185 | }); 186 | suites.push(bench); 187 | const setups = []; 188 | bench.setup(setups); 189 | 190 | Adapters.forEach(adapterDef => { 191 | const adapterName = adapterDef.name || adapterDef.type; 192 | const actionName = `${adapterDef.svcName}.get`; 193 | 194 | let docs = null; 195 | setups.push(async () => { 196 | docs = await broker.call(`${adapterDef.svcName}.find`); 197 | }); 198 | 199 | let c = 0; 200 | bench[adapterDef.ref ? "ref" : "add"](adapterName, done => { 201 | const entity = docs[Math.floor(Math.random() * docs.length)]; 202 | return broker.call(actionName, { id: entity.id }).then(done).catch(err => console.error(err)); 203 | }); 204 | }); 205 | })(); 206 | 207 | // --- ENTITY UPDATING --- 208 | (function () { 209 | const bench = benchmark.createSuite("Entity updating", { 210 | description: "This test calls the `users.update` service action to update a entity.", 211 | meta: { 212 | type: "update" 213 | } 214 | }); 215 | suites.push(bench); 216 | const setups = []; 217 | bench.setup(setups); 218 | 219 | Adapters.forEach(adapterDef => { 220 | const adapterName = adapterDef.name || adapterDef.type; 221 | const actionName = `${adapterDef.svcName}.update`; 222 | 223 | let docs = null; 224 | setups.push(async () => { 225 | docs = await broker.call(`${adapterDef.svcName}.find`); 226 | }); 227 | 228 | let c = 0; 229 | bench[adapterDef.ref ? "ref" : "add"](adapterName, done => { 230 | const entity = docs[Math.floor(Math.random() * docs.length)]; 231 | const newStatus = Math.round(Math.random()); 232 | return broker.call(actionName, { id: entity.id, status: newStatus }).then(done).catch(err => console.error(err)); 233 | }); 234 | }); 235 | })(); 236 | 237 | // --- ENTITY DELETING --- 238 | (function () { 239 | const bench = benchmark.createSuite("Entity deleting", { 240 | description: "This test calls the `users.remove` service action to delete a random entity.", 241 | meta: { 242 | type: "remove" 243 | } 244 | }); 245 | suites.push(bench); 246 | const setups = []; 247 | bench.setup(setups); 248 | 249 | Adapters.forEach(adapterDef => { 250 | const adapterName = adapterDef.name || adapterDef.type; 251 | const actionName = `${adapterDef.svcName}.remove`; 252 | 253 | let docs = null; 254 | setups.push(async () => { 255 | docs = await broker.call(`${adapterDef.svcName}.find`); 256 | }); 257 | 258 | let c = 0; 259 | bench[adapterDef.ref ? "ref" : "add"](adapterName, done => { 260 | const entity = docs[Math.floor(Math.random() * docs.length)]; 261 | return broker.call(actionName, { id: entity.id }).catch(done).then(done).catch(err => console.error(err)); 262 | }); 263 | }); 264 | })(); 265 | 266 | async function run() { 267 | await broker.start(); 268 | try { 269 | console.log("Running suites..."); 270 | await benchmark.run(suites); 271 | } finally { 272 | await broker.stop(); 273 | } 274 | console.log("Done."); 275 | 276 | process.exit(0); 277 | } 278 | 279 | run(); 280 | -------------------------------------------------------------------------------- /dev.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const moduleName = process.argv[2]; 4 | const example = process.argv[3] || "simple"; 5 | process.argv.splice(2, 2); 6 | 7 | require("./packages/" + moduleName + "/examples/" + example); -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "lerna": "2.0.0-rc.5", 3 | "packages": [ 4 | "packages/*" 5 | ], 6 | "version": "independent", 7 | "command": { 8 | "publish": { 9 | "ignoreChanges": [ 10 | "*.md", 11 | "package-lock.json" 12 | ] 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "moleculer-db-addons", 3 | "description": ":gift: Official database addons for Moleculer framework", 4 | "version": "1.0.0", 5 | "scripts": { 6 | "setup": "npm install && lerna bootstrap", 7 | "setup-no-old-node": "npm install && lerna bootstrap --ignore moleculer-db-adapter-mongoose", 8 | "clean": "lerna clean", 9 | "dev": "nodemon dev.js", 10 | "demo": "node dev.js", 11 | "test:unit": "jest --testMatch \"**/unit/**/*.spec.js\" --coverage", 12 | "test:integration": "jest --testMatch \"**/integration/**/*.spec.js\" --runInBand --coverage", 13 | "ci": "jest --watch", 14 | "coverall": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js", 15 | "init": "cd packages && moleculer init addon", 16 | "bench": "node benchmark/index.js", 17 | "deps": "lerna exec --concurrency 1 npm run deps", 18 | "audit": "lerna exec --concurrency 1 npm audit fix", 19 | "release": "lerna publish", 20 | "readme": "node readme-generator.js" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/moleculerjs/moleculer-db.git" 25 | }, 26 | "keywords": [], 27 | "author": "MoleculerJS", 28 | "license": "MIT", 29 | "homepage": "https://github.com/moleculerjs/moleculer-db#readme", 30 | "devDependencies": { 31 | "benchmarkify": "^4.0.0", 32 | "coveralls": "^3.1.1", 33 | "eslint": "^8.0.1", 34 | "fakerator": "^0.3.6", 35 | "glob": "^7.2.0", 36 | "jest": "^27.2.5", 37 | "jest-cli": "^27.2.5", 38 | "lerna": "^4.0.0", 39 | "lodash": "^4.17.21", 40 | "markdown-magic": "^2.5.2", 41 | "moleculer": "^0.14.17", 42 | "nodemon": "^2.0.13", 43 | "sequelize": "^6.33.0" 44 | }, 45 | "dependencies": { 46 | "moleculer-cli": "^0.8.1" 47 | }, 48 | "jest": { 49 | "testEnvironment": "node", 50 | "coveragePathIgnorePatterns": [ 51 | "/node_modules/", 52 | "/test/services/" 53 | ] 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-couchdb-nano/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/ 3 | node_modules/ 4 | coverage/ 5 | npm-debug.log 6 | stats.json 7 | yarn-error.log -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-couchdb-nano/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | # 0.1.7 (2020-02-02) 3 | 4 | ## Changes 5 | - add missing Bluebird dependency 6 | 7 | -------------------------------------------------- 8 | -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-couchdb-nano/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Mr. Kutin 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 | -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-couchdb-nano/README.md: -------------------------------------------------------------------------------- 1 | ![Moleculer logo](http://moleculer.services/images/banner.png) 2 | 3 | # moleculer-db-adapter-couchdb-nano [![NPM version](https://img.shields.io/npm/v/moleculer-db-adapter-couchdb-nano.svg)](https://www.npmjs.com/package/moleculer-db-adapter-couchdb-nano) 4 | 5 | CouchDB [Nano](https://github.com/apache/couchdb-nano) adapter for Moleculer DB service. 6 | 7 | ## Features 8 | - Schemaless adapter 9 | - CouchDB Nano driver v7 10 | 11 | ## Install 12 | 13 | ```bash 14 | $ npm install moleculer-db moleculer-db-adapter-couchdb-nano --save 15 | ``` 16 | 17 | ## Usage 18 | 19 | ```js 20 | "use strict"; 21 | 22 | const { ServiceBroker } = require("moleculer"); 23 | const DbService = require("moleculer-db"); 24 | const CouchDBAdapter = require("moleculer-db-adapter-couchdb-nano"); 25 | 26 | const broker = new ServiceBroker(); 27 | 28 | // Create a CouchDB service for `blog-post` entities 29 | broker.createService({ 30 | name: "blog", 31 | collection: "posts", 32 | adapter: new CouchDBAdapter("couchdb://localhost:5984"), 33 | mixins: [DbService] 34 | }); 35 | 36 | broker.start() 37 | // Create a new post 38 | .then(() => broker.call("posts.create", { 39 | title: "My first post", 40 | content: "Lorem ipsum...", 41 | votes: 0 42 | })) 43 | 44 | // Get all posts 45 | .then(() => broker.call("posts.find").then(console.log)); 46 | ``` 47 | 48 | ## Options 49 | 50 | **Example with default connection to localhost:5984 ** 51 | ```js 52 | new CouchDBAdapter() 53 | ``` 54 | 55 | **Example with connection URI & options** 56 | ```js 57 | new CouchDBAdapter("couchdb://localhost:5984", { 58 | //any opts supported by Nano 59 | }) 60 | ``` 61 | 62 | # Test 63 | ``` 64 | $ npm test 65 | ``` 66 | 67 | In development with watching 68 | 69 | ``` 70 | $ npm run ci 71 | ``` 72 | 73 | # License 74 | The project is available under the [MIT license](https://tldrlegal.com/license/mit-license). 75 | 76 | # Contact 77 | Copyright (c) 2018 Mr. Kutin 78 | -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-couchdb-nano/examples/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const moduleName = process.argv[2] || "simple"; 4 | process.argv.splice(2, 1); 5 | 6 | require("./" + moduleName); -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-couchdb-nano/examples/integration/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const {ServiceBroker} = require("moleculer"); 4 | const StoreService = require("../../../moleculer-db/index"); 5 | const CouchAdapter = require("../../index"); 6 | const ModuleChecker = require("../../../moleculer-db/test/checker"); 7 | 8 | const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); 9 | 10 | // Create broker 11 | const broker = new ServiceBroker({ 12 | logger: console, 13 | logLevel: "debug" 14 | }); 15 | 16 | // Load my service 17 | broker.createService(StoreService, { 18 | name: "posts", 19 | adapter: new CouchAdapter("couchdb://127.0.0.1:5984"), 20 | settings: { 21 | fields: ["_id", "title", "content", "votes", "status", "updatedAt"] 22 | }, 23 | 24 | afterConnected() { 25 | this.logger.info("Connected successfully"); 26 | return this.adapter.clear().then(() => { 27 | this.adapter.db.createIndex({ 28 | index: {fields: ["votes", "title"]}, 29 | name: "votes-title" 30 | }); 31 | }).then(() => start()); 32 | } 33 | }); 34 | 35 | const checker = new ModuleChecker(11); 36 | 37 | // Start checks 38 | function start() { 39 | Promise.resolve() 40 | .then(() => delay(500)) 41 | .then(() => checker.execute()) 42 | .catch(console.error) 43 | .then(() => broker.stop()) 44 | .then(() => checker.printTotal()); 45 | } 46 | 47 | // --- TEST CASES --- 48 | 49 | let id = []; 50 | 51 | // Count of posts 52 | checker.add("COUNT", () => broker.call("posts.count"), res => { 53 | console.log(res); 54 | return res === 0; 55 | }); 56 | 57 | // Create new Posts 58 | checker.add("--- CREATE ---", () => broker.call("posts.create", { 59 | title: "Hello", 60 | content: "Post content", 61 | votes: 2, 62 | status: true 63 | }), doc => { 64 | id = doc._id; 65 | console.log("Saved: ", doc); 66 | return doc._id && doc.title === "Hello" && doc.content === "Post content" && doc.votes === 2 && doc.status === true; 67 | }); 68 | 69 | // List posts 70 | checker.add("--- FIND ---", () => broker.call("posts.find", {fields: ["_id", "title"]}), res => { 71 | console.log(res); 72 | return res.length === 1 && res[0]._id === id && !res[0].content && !res[0].votes && !res[0].status; 73 | }); 74 | 75 | // Get a post 76 | checker.add("--- GET ---", () => broker.call("posts.get", {id}), res => { 77 | console.log(res); 78 | return res._id === id; 79 | }); 80 | 81 | // Vote a post 82 | checker.add("--- VOTE ---", () => broker.call("posts.update", {id, votes: 3}), res => { 83 | console.log(res); 84 | return res._id === id && res.votes === 3; 85 | }); 86 | 87 | // Update a posts 88 | checker.add("--- UPDATE ---", () => broker.call("posts.update", { 89 | id, 90 | title: "Hello 2", 91 | content: "Post content 2", 92 | updatedAt: new Date() 93 | }), doc => { 94 | console.log(doc); 95 | return doc._id && doc.title === "Hello 2" && doc.content === "Post content 2" && doc.votes === 3 && doc.status === true && doc.updatedAt; 96 | }); 97 | 98 | // Get a post 99 | checker.add("--- GET ---", () => broker.call("posts.get", {id}), doc => { 100 | console.log(doc); 101 | return doc._id === id && doc.title === "Hello 2" && doc.votes === 3; 102 | }); 103 | 104 | // Unvote a post 105 | checker.add("--- UNVOTE ---", () => broker.call("posts.update", {id, votes: 2}), res => { 106 | console.log(res); 107 | return res._id === id && res.votes === 2; 108 | }); 109 | 110 | // Count of posts 111 | checker.add("--- COUNT ---", () => broker.call("posts.count"), res => { 112 | console.log(res); 113 | return res === 1; 114 | }); 115 | 116 | // Remove a post 117 | checker.add("--- REMOVE ---", () => broker.call("posts.remove", {id}), res => { 118 | console.log(res); 119 | return res._id === id; 120 | }); 121 | 122 | // Count of posts 123 | checker.add("--- COUNT ---", () => broker.call("posts.count"), res => { 124 | console.log(res); 125 | return res === 0; 126 | }); 127 | 128 | broker.start(); 129 | -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-couchdb-nano/examples/simple/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const {ServiceBroker} = require("moleculer"); 4 | const StoreService = require("../../../moleculer-db/index"); 5 | const ModuleChecker = require("../../../moleculer-db/test/checker"); 6 | const CouchAdapter = require("../../index"); 7 | 8 | const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); 9 | 10 | // Create broker 11 | const broker = new ServiceBroker({ 12 | logger: console, 13 | logLevel: "debug" 14 | }); 15 | let adapter; 16 | 17 | // Load my service 18 | broker.createService(StoreService, { 19 | name: "posts", 20 | adapter: new CouchAdapter("couchdb://127.0.0.1:5984"), 21 | settings: {}, 22 | 23 | afterConnected() { 24 | this.logger.info("Connected successfully"); 25 | adapter = this.adapter; 26 | return this.adapter.clear() 27 | .then(() => { 28 | this.adapter.db.createIndex({ 29 | index: {fields: ["votes", "title"]}, 30 | name: "votes-title" 31 | }); 32 | }) 33 | .then(() => start()); 34 | } 35 | }); 36 | 37 | const checker = new ModuleChecker(21); 38 | 39 | // Start checks 40 | function start() { 41 | Promise.resolve() 42 | .then(() => delay(500)) 43 | .then(() => checker.execute()) 44 | .catch(console.error) 45 | .then(() => broker.stop()) 46 | .then(() => checker.printTotal()); 47 | } 48 | 49 | // --- TEST CASES --- 50 | 51 | let ids = []; 52 | let date = new Date(); 53 | 54 | // Count of posts 55 | checker.add("COUNT", () => adapter.count(), res => { 56 | console.log(res); 57 | return res === 0; 58 | }); 59 | 60 | // Insert a new Post 61 | checker.add("INSERT", () => adapter.insert({ 62 | title: "Hello", 63 | content: "Post content", 64 | votes: 3, 65 | status: true, 66 | createdAt: date 67 | }), doc => { 68 | ids[0] = doc._id; 69 | console.log("Saved: ", doc); 70 | return doc._id && doc.title === "Hello" && doc.content === "Post content" && doc.votes === 3 && doc.status === true && new Date(doc.createdAt).getTime() === date.getTime(); 71 | }); 72 | 73 | // Find 74 | checker.add("FIND", () => adapter.find({}), res => { 75 | console.log(res); 76 | return res.length === 1 && res[0]._id === ids[0]; 77 | }); 78 | 79 | // Find by ID 80 | checker.add("GET", () => adapter.findById(ids[0]), res => { 81 | console.log(res); 82 | return res._id === ids[0]; 83 | }); 84 | 85 | // Count of posts 86 | checker.add("COUNT", () => adapter.count(), res => { 87 | console.log(res); 88 | return res === 1; 89 | }); 90 | 91 | // Insert many new Posts 92 | checker.add("INSERT MANY", () => adapter.insertMany([ 93 | {title: "Second", content: "Second post content", votes: 8, status: true, createdAt: new Date()}, 94 | {title: "Last", content: "Last document", votes: 1, status: false, createdAt: new Date()} 95 | ]), docs => { 96 | console.log("Saved: ", docs); 97 | ids[1] = docs[0]._id; 98 | ids[2] = docs[1]._id; 99 | 100 | return [ 101 | docs.length === 2, 102 | ids[1] && docs[0].title === "Second" && docs[0].votes === 8, 103 | ids[1] && docs[1].title === "Last" && docs[1].votes === 1 && docs[1].status === false 104 | ]; 105 | }); 106 | 107 | // Count of posts 108 | checker.add("COUNT", () => adapter.count(), res => { 109 | console.log(res); 110 | return res === 3; 111 | }); 112 | 113 | // Find 114 | checker.add("FIND by selector", () => adapter.find({selector: {title: "Last"}}), res => { 115 | console.log(res); 116 | return res.length === 1 && res[0]._id === ids[2]; 117 | }); 118 | 119 | // Find 120 | checker.add("FIND by limit, sort, query", () => adapter.find({limit: 1, sort: ["votes", "title"], skip: 1}), res => { 121 | console.log(res); 122 | return res.length === 1 && res[0]._id === ids[0]; 123 | }); 124 | 125 | // Find 126 | checker.add("FIND by query ($gt)", () => adapter.find({selector: {votes: {$gt: 2}}}), res => { 127 | console.log(res); 128 | return res.length === 2; 129 | }); 130 | 131 | // Find 132 | checker.add("COUNT by query ($gt)", () => adapter.count({selector: {votes: {$gt: 2}}}), res => { 133 | console.log(res); 134 | return res === 2; 135 | }); 136 | 137 | // Find by IDs 138 | checker.add("GET BY IDS", () => adapter.findByIds([ids[2], ids[0]]), res => { 139 | console.log(res); 140 | return res.length === 2; 141 | }); 142 | 143 | // Update a posts 144 | checker.add("UPDATE", () => adapter.updateById(ids[2], { 145 | title: "Last 2", 146 | updatedAt: new Date(), 147 | status: true 148 | }), doc => { 149 | console.log("Updated: ", doc); 150 | return doc._id && doc.title === "Last 2" && doc.content === "Last document" && doc.votes === 1 && doc.status === true && doc.updatedAt; 151 | }); 152 | 153 | // Update by query 154 | checker.add("UPDATE BY QUERY", () => adapter.updateMany({votes: {$lt: 5}}, 155 | { 156 | status: false 157 | }), count => { 158 | console.log("Updated: ", count); 159 | return count === 2; 160 | }); 161 | 162 | // Remove by query 163 | checker.add("REMOVE BY QUERY", () => adapter.removeMany({votes: {$lt: 5}}), count => { 164 | console.log("Removed: ", count); 165 | return count === 2; 166 | }); 167 | 168 | // Count of posts 169 | checker.add("COUNT", () => adapter.count(), res => { 170 | console.log(res); 171 | return res === 1; 172 | }); 173 | 174 | // Remove by ID 175 | checker.add("REMOVE BY ID", () => adapter.removeById(ids[1]), doc => { 176 | console.log("Removed: ", doc); 177 | return doc && doc._id === ids[1]; 178 | }); 179 | 180 | // Count of posts 181 | checker.add("COUNT", () => adapter.count(), res => { 182 | console.log(res); 183 | return res === 0; 184 | }); 185 | 186 | // Clear 187 | checker.add("CLEAR", () => adapter.clear(), res => { 188 | console.log(res); 189 | return res === 0; 190 | }); 191 | 192 | broker.start(); 193 | -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-couchdb-nano/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer-db-adapter-couchdb-nano 3 | * Copyright (c) 2018 Mr. Kutin (https://github.com/mrkutin/moleculer-db-adapter-couchdb-nano) 4 | * MIT Licensed 5 | */ 6 | "use strict"; 7 | module.exports = require("./src"); -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-couchdb-nano/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "moleculer-db-adapter-couchdb-nano", 3 | "version": "0.1.15", 4 | "description": "CouchDB Nano adapter for Moleculer DB service.", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "nodemon examples/index.js", 8 | "ci": "jest --watch", 9 | "test": "jest --coverage", 10 | "lint": "eslint --ext=.js src test", 11 | "deps": "npm-check -u", 12 | "readme": "node node_modules/moleculer-docgen/index.js -t README.md src/index.js", 13 | "coverall": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js" 14 | }, 15 | "keywords": [ 16 | "microservice", 17 | "moleculer", 18 | "couchdb" 19 | ], 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/moleculerjs/moleculer-db.git" 23 | }, 24 | "homepage": "https://github.com/moleculerjs/moleculer-db/tree/master/packages/moleculer-db-adapter-couchdb-nano#readme", 25 | "author": "Mr. Kutin", 26 | "license": "MIT", 27 | "peerDependencies": { 28 | "moleculer": "^0.12.0 || ^0.13.0 || ^0.14.0 || ^0.15.0" 29 | }, 30 | "devDependencies": { 31 | "benchmarkify": "^3.0.0", 32 | "coveralls": "^3.1.1", 33 | "eslint": "^8.21.0", 34 | "jest": "^27.2.5", 35 | "jest-cli": "^27.2.5", 36 | "lolex": "^6.0.0", 37 | "moleculer": "^0.14.22", 38 | "moleculer-docgen": "^0.3.0", 39 | "nodemon": "^2.0.19", 40 | "npm-check": "^6.0.1" 41 | }, 42 | "jest": { 43 | "testEnvironment": "node", 44 | "coveragePathIgnorePatterns": [ 45 | "/node_modules/", 46 | "/test/services/" 47 | ] 48 | }, 49 | "engines": { 50 | "node": ">= 8.x.x" 51 | }, 52 | "dependencies": { 53 | "nano": "^9.0.5" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-mongo/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | coverage/ 4 | npm-debug.log 5 | stats.json 6 | yarn-error.log -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-mongo/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | # 0.4.11 (2021-02-15) 3 | 4 | ## Changes 5 | - allow only 12 character ObjectID strings [#249](https://github.com/moleculerjs/moleculer-db/pull/249) 6 | 7 | -------------------------------------------------- 8 | 9 | # 0.4.6 (2020-02-02) 10 | 11 | ## Changes 12 | - add missing Bluebird dependency 13 | 14 | -------------------------------------------------- 15 | 16 | # 0.4.5 (2019-08-14) 17 | 18 | ## Changes 19 | - Fix issue in full-text searching [#122](https://github.com/moleculerjs/moleculer-db/issues/122) 20 | 21 | -------------------------------------------------- 22 | 23 | # 0.4.3 (2019-07-07) 24 | 25 | ## Changes 26 | - Add `dbName` parameter to constructor. Example: `adapter: new MongoAdapter("mongodb://127.0.0.1", { useNewUrlParser: true }, "moleculer-db-demo")` 27 | 28 | -------------------------------------------------- 29 | 30 | # 0.4.0 (2018-04-08) 31 | 32 | ## Breaking changes 33 | - fix wrong method name from `ojectIDToString` to `objectIDToString` 34 | 35 | -------------------------------------------------- 36 | 37 | # 0.3.0 (2018-03-28) 38 | 39 | ## Breaking changes 40 | - Update `mongodb` lib to v3.0.5 41 | - Changed constructor signature (compatible with mongodb driver) 42 | 43 | **Example with connection URI** 44 | ```js 45 | new MongoDBAdapter("mongodb://127.0.0.1/moleculer-db") 46 | ``` 47 | 48 | **Example with connection URI & options** 49 | ```js 50 | new MongoDBAdapter("mongodb://db-server-hostname/my-db", { 51 | keepAlive: 1 52 | }) 53 | ``` 54 | 55 | -------------------------------------------------- 56 | -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-mongo/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 MoleculerJS 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 | -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-mongo/README.md: -------------------------------------------------------------------------------- 1 | ![Moleculer logo](http://moleculer.services/images/banner.png) 2 | 3 | # moleculer-db-adapter-mongo [![NPM version](https://img.shields.io/npm/v/moleculer-db-adapter-mongo.svg)](https://www.npmjs.com/package/moleculer-db-adapter-mongo) 4 | 5 | MongoDB native adapter for Moleculer DB service. 6 | 7 | ## Features 8 | - schemaless adapter 9 | - MongoDB driver v3.0 10 | 11 | ## Install 12 | 13 | ```bash 14 | $ npm install moleculer-db moleculer-db-adapter-mongo --save 15 | ``` 16 | 17 | ## Usage 18 | 19 | ```js 20 | "use strict"; 21 | 22 | const { ServiceBroker } = require("moleculer"); 23 | const DbService = require("moleculer-db"); 24 | const MongoDBAdapter = require("moleculer-db-adapter-mongo"); 25 | 26 | const broker = new ServiceBroker(); 27 | 28 | // Create a Mongoose service for `post` entities 29 | broker.createService({ 30 | name: "posts", 31 | mixins: [DbService], 32 | adapter: new MongoDBAdapter("mongodb://127.0.0.1/moleculer-demo"), 33 | collection: "posts" 34 | }); 35 | 36 | 37 | broker.start() 38 | // Create a new post 39 | .then(() => broker.call("posts.create", { 40 | title: "My first post", 41 | content: "Lorem ipsum...", 42 | votes: 0 43 | })) 44 | 45 | // Get all posts 46 | .then(() => broker.call("posts.find").then(console.log)); 47 | ``` 48 | 49 | ## Options 50 | 51 | **Example with connection URI** 52 | ```js 53 | new MongoDBAdapter("mongodb://127.0.0.1/moleculer-db") 54 | ``` 55 | 56 | **Example with connection URI & options** 57 | ```js 58 | new MongoDBAdapter("mongodb://db-server-hostname/my-db", { 59 | keepAlive: 1 60 | }) 61 | ``` 62 | 63 | # Test 64 | ``` 65 | $ npm test 66 | ``` 67 | 68 | In development with watching 69 | 70 | ``` 71 | $ npm run ci 72 | ``` 73 | 74 | # License 75 | The project is available under the [MIT license](https://tldrlegal.com/license/mit-license). 76 | 77 | # Contact 78 | Copyright (c) 2016-2024 MoleculerJS 79 | 80 | [![@moleculerjs](https://img.shields.io/badge/github-moleculerjs-green.svg)](https://github.com/moleculerjs) [![@MoleculerJS](https://img.shields.io/badge/twitter-MoleculerJS-blue.svg)](https://twitter.com/MoleculerJS) 81 | -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-mongo/examples/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const moduleName = process.argv[2] || "simple"; 4 | process.argv.splice(2, 1); 5 | 6 | require("./" + moduleName); -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-mongo/examples/integration/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { ServiceBroker } = require("moleculer"); 4 | const StoreService = require("../../../moleculer-db/index"); 5 | const MongoAdapter = require("../../index"); 6 | const ModuleChecker = require("../../../moleculer-db/test/checker"); 7 | 8 | const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); 9 | 10 | // Create broker 11 | const broker = new ServiceBroker({ 12 | logger: console, 13 | logLevel: "debug" 14 | }); 15 | 16 | // Load my service 17 | broker.createService(StoreService, { 18 | name: "posts", 19 | adapter: new MongoAdapter("mongodb://127.0.0.1/moleculer-db-demo", { useNewUrlParser: true }), 20 | collection: "posts", 21 | settings: { 22 | fields: ["_id", "title", "content", "votes", "status", "updatedAt"] 23 | }, 24 | 25 | actions: { 26 | vote(ctx) { 27 | return this.Promise.resolve(ctx) 28 | .then(ctx => this.adapter.updateById(ctx.params.id, { $inc: { votes: 1 } })); 29 | }, 30 | 31 | unvote(ctx) { 32 | return this.Promise.resolve(ctx) 33 | .then(ctx => this.adapter.updateById(ctx.params.id, { $inc: { votes: -1 } })); 34 | } 35 | }, 36 | 37 | afterConnected() { 38 | return this.adapter.clear().then(() => { 39 | this.adapter.collection.createIndex( { title: "text", content: "text" } ); 40 | }).then(() => start()); 41 | } 42 | }); 43 | 44 | const checker = new ModuleChecker(11); 45 | 46 | // Start checks 47 | function start() { 48 | Promise.resolve() 49 | .then(() => delay(500)) 50 | .then(() => checker.execute()) 51 | .catch(console.error) 52 | .then(() => broker.stop()) 53 | .then(() => checker.printTotal()); 54 | } 55 | 56 | // --- TEST CASES --- 57 | 58 | let id =[]; 59 | 60 | // Count of posts 61 | checker.add("COUNT", () => broker.call("posts.count"), res => { 62 | console.log(res); 63 | return res == 0; 64 | }); 65 | 66 | // Create new Posts 67 | checker.add("--- CREATE ---", () => broker.call("posts.create", { title: "Hello", content: "Post content", votes: 2, status: true }), doc => { 68 | id = doc._id; 69 | console.log("Saved: ", doc); 70 | return doc._id && doc.title === "Hello" && doc.content === "Post content" && doc.votes === 2 && doc.status === true; 71 | }); 72 | 73 | // List posts 74 | checker.add("--- FIND ---", () => broker.call("posts.find", { fields: ["_id", "title"]}), res => { 75 | console.log(res); 76 | return res.length == 1 && res[0]._id == id && res[0].content == null && res[0].votes == null && res[0].status == null; 77 | }); 78 | 79 | // Get a post 80 | checker.add("--- GET ---", () => broker.call("posts.get", { id }), res => { 81 | console.log(res); 82 | return res._id == id; 83 | }); 84 | 85 | // Vote a post 86 | checker.add("--- VOTE ---", () => broker.call("posts.vote", { 87 | id 88 | }), res => { 89 | console.log(res); 90 | return res._id == id && res.votes === 3; 91 | }); 92 | 93 | // Update a posts 94 | checker.add("--- UPDATE ---", () => broker.call("posts.update", { 95 | id, 96 | title: "Hello 2", 97 | content: "Post content 2", 98 | updatedAt: new Date() 99 | }), doc => { 100 | console.log(doc); 101 | return doc._id && doc.title === "Hello 2" && doc.content === "Post content 2" && doc.votes === 3 && doc.status === true && doc.updatedAt; 102 | }); 103 | 104 | // Get a post 105 | checker.add("--- GET ---", () => broker.call("posts.get", { id }), doc => { 106 | console.log(doc); 107 | return doc._id == id && doc.title == "Hello 2" && doc.votes === 3; 108 | }); 109 | 110 | // Unvote a post 111 | checker.add("--- UNVOTE ---", () => broker.call("posts.unvote", { 112 | id 113 | }), res => { 114 | console.log(res); 115 | return res._id == id && res.votes === 2; 116 | }); 117 | 118 | // Count of posts 119 | checker.add("--- COUNT ---", () => broker.call("posts.count"), res => { 120 | console.log(res); 121 | return res == 1; 122 | }); 123 | 124 | // Remove a post 125 | checker.add("--- REMOVE ---", () => broker.call("posts.remove", { id }), res => { 126 | console.log(res); 127 | return res._id == id; 128 | }); 129 | 130 | // Count of posts 131 | checker.add("--- COUNT ---", () => broker.call("posts.count"), res => { 132 | console.log(res); 133 | return res == 0; 134 | }); 135 | 136 | 137 | broker.start(); 138 | -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-mongo/examples/search/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { ServiceBroker } = require("moleculer"); 4 | const DbService = require("../../../moleculer-db/index"); 5 | const MongoDBAdapter = require("../../index"); 6 | const Fakerator = require("fakerator")(); 7 | 8 | // Create broker 9 | let broker = new ServiceBroker({ 10 | logger: console, 11 | logLevel: "debug" 12 | }); 13 | 14 | // Load my service 15 | broker.createService({ 16 | name: "users", 17 | mixins: [DbService], 18 | adapter: new MongoDBAdapter("mongodb://127.0.0.1/search-test"), 19 | collection: "users", 20 | 21 | methods: { 22 | seed() { 23 | Fakerator.seed(12345); 24 | return this.adapter.insertMany(Array(100).fill(0).map(() => { 25 | return { 26 | username: Fakerator.internet.userName(), 27 | name: Fakerator.names.name(), 28 | zip: Fakerator.random.number(1000,9999) 29 | }; 30 | })); 31 | }, 32 | }, 33 | 34 | /** 35 | * Actions 36 | */ 37 | actions: { 38 | 39 | /** 40 | * Perform a textual search with 'find' method 41 | */ 42 | searchWithFind(ctx) { 43 | return ctx.call("users.find", { 44 | search: "Dianne" 45 | }); 46 | }, 47 | 48 | /** 49 | * Perform a textual search with 'list' method 50 | */ 51 | searchWithList(ctx) { 52 | return ctx.call("users.list", { 53 | search: "Dianne" 54 | }); 55 | } 56 | 57 | }, 58 | 59 | afterConnected() { 60 | return this.adapter.clear() 61 | .then(() => this.adapter.collection.createIndex( { name: "text" } )) 62 | .then(() => this.seed()); 63 | 64 | } 65 | }); 66 | 67 | broker.start() 68 | .then(() => broker.repl()) 69 | .then(() => { 70 | return broker.call("users.list", { 71 | search: "Dianne" 72 | }).then(res => console.log(res)); 73 | }) 74 | .catch(err => broker.logger.error(err)); 75 | -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-mongo/examples/simple/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { ServiceBroker } = require("moleculer"); 4 | const StoreService = require("../../../moleculer-db/index"); 5 | const ModuleChecker = require("../../../moleculer-db/test/checker"); 6 | const MongoAdapter = require("../../index"); 7 | 8 | const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); 9 | 10 | // Create broker 11 | const broker = new ServiceBroker({ 12 | logger: console, 13 | logLevel: "debug" 14 | }); 15 | let adapter; 16 | 17 | // Load my service 18 | broker.createService(StoreService, { 19 | name: "posts", 20 | adapter: new MongoAdapter("mongodb://127.0.0.1", { useNewUrlParser: true }, "moleculer-db-demo"), 21 | collection: "posts", 22 | settings: {}, 23 | 24 | afterConnected() { 25 | adapter = this.adapter; 26 | return this.adapter.clear().then(() => { 27 | this.adapter.collection.createIndex( { title: "text", content: "text" } ); 28 | }).then(() => start()); 29 | } 30 | }); 31 | 32 | const checker = new ModuleChecker(24); 33 | 34 | // Start checks 35 | function start() { 36 | return Promise.resolve() 37 | .then(() => delay(500)) 38 | .then(() => checker.execute()) 39 | .catch(console.error) 40 | .then(() => broker.stop()) 41 | .then(() => checker.printTotal()); 42 | } 43 | 44 | // --- TEST CASES --- 45 | 46 | const ids =[]; 47 | const date = new Date(); 48 | 49 | // Count of posts 50 | checker.add("COUNT", () => adapter.count(), res => { 51 | console.log(res); 52 | return res == 0; 53 | }); 54 | 55 | // Insert a new Post 56 | checker.add("INSERT", () => adapter.insert({ title: "Hello", content: "Post content", votes: 3, status: true, createdAt: date }), doc => { 57 | ids[0] = doc._id.toHexString(); 58 | console.log("Saved: ", doc); 59 | return doc._id && doc.title === "Hello" && doc.content === "Post content" && doc.votes === 3 && doc.status === true && doc.createdAt === date; 60 | }); 61 | 62 | // Find 63 | checker.add("FIND", () => adapter.find({}), res => { 64 | console.log(res); 65 | return res.length == 1 && res[0]._id.toHexString() == ids[0]; 66 | }); 67 | 68 | // Find by ID 69 | checker.add("GET", () => adapter.findById(ids[0]), res => { 70 | console.log(res); 71 | return res._id.toHexString() == ids[0]; 72 | }); 73 | 74 | // Count of posts 75 | checker.add("COUNT", () => adapter.count(), res => { 76 | console.log(res); 77 | return res == 1; 78 | }); 79 | 80 | // Insert many new Posts 81 | checker.add("INSERT MANY", () => adapter.insertMany([ 82 | { title: "Second", content: "Second post content", votes: 8, status: true, createdAt: new Date() }, 83 | { title: "Last", content: "Last document", votes: 1, status: false, createdAt: new Date() } 84 | ]), docs => { 85 | console.log("Saved: ", docs); 86 | ids[1] = docs[0]._id.toHexString(); 87 | ids[2] = docs[1]._id.toHexString(); 88 | 89 | return [ 90 | docs.length == 2, 91 | ids[1] && docs[0].title === "Second" && docs[0].votes === 8, 92 | ids[1] && docs[1].title === "Last" && docs[1].votes === 1 && docs[1].status === false 93 | ]; 94 | }); 95 | 96 | // Count of posts 97 | checker.add("COUNT", () => adapter.count(), res => { 98 | console.log(res); 99 | return res == 3; 100 | }); 101 | 102 | // Find 103 | checker.add("FIND by query", () => adapter.find({ query: { title: "Last" } }), res => { 104 | console.log(res); 105 | return res.length == 1 && res[0]._id.toHexString() == ids[2]; 106 | }); 107 | 108 | // Find 109 | checker.add("FIND by limit, sort, query", () => adapter.find({ limit: 1, sort: ["votes", "-title"], offset: 1 }), res => { 110 | console.log(res); 111 | return res.length == 1 && res[0]._id.toHexString() == ids[0]; 112 | }); 113 | 114 | // Find 115 | checker.add("FIND by query ($gt)", () => adapter.find({ query: { votes: { $gt: 2 } } }), res => { 116 | console.log(res); 117 | return res.length == 2; 118 | }); 119 | 120 | // Find 121 | checker.add("COUNT by query ($gt)", () => adapter.count({ query: { votes: { $gt: 2 } } }), res => { 122 | console.log(res); 123 | return res == 2; 124 | }); 125 | 126 | // Find 127 | checker.add("FIND by text search", () => adapter.find({ search: "content" }), res => { 128 | console.log(res); 129 | return [ 130 | res.length == 2, 131 | res[0]._score < 1 && res[0].title === "Hello", 132 | res[1]._score < 1 && res[1].title === "Second" 133 | ]; 134 | }); 135 | 136 | // Find by IDs 137 | checker.add("GET BY IDS", () => adapter.findByIds([ids[2], ids[0]]), res => { 138 | console.log(res); 139 | return res.length == 2; 140 | }); 141 | 142 | // Update a posts 143 | checker.add("UPDATE", () => adapter.updateById(ids[2], { $set: { 144 | title: "Last 2", 145 | updatedAt: new Date(), 146 | status: true 147 | }}), doc => { 148 | console.log("Updated: ", doc); 149 | return doc._id && doc.title === "Last 2" && doc.content === "Last document" && doc.votes === 1 && doc.status === true && doc.updatedAt; 150 | }); 151 | 152 | // Update by query 153 | checker.add("UPDATE BY QUERY", () => adapter.updateMany({ votes: { $lt: 5 }}, { 154 | $set: { status: false } 155 | }), count => { 156 | console.log("Updated: ", count); 157 | return count == 2; 158 | }); 159 | 160 | // Remove by query 161 | checker.add("REMOVE BY QUERY", () => adapter.removeMany({ votes: { $lt: 5 }}), count => { 162 | console.log("Removed: ", count); 163 | return count == 2; 164 | }); 165 | 166 | // Count of posts 167 | checker.add("COUNT", () => adapter.count(), res => { 168 | console.log(res); 169 | return res == 1; 170 | }); 171 | 172 | // Remove by ID 173 | checker.add("REMOVE BY ID", () => adapter.removeById(ids[1]), doc => { 174 | console.log("Removed: ", doc); 175 | return doc && doc._id.toHexString() == ids[1]; 176 | }); 177 | 178 | // Count of posts 179 | checker.add("COUNT", () => adapter.count(), res => { 180 | console.log(res); 181 | return res == 0; 182 | }); 183 | 184 | // Clear 185 | checker.add("CLEAR", () => adapter.clear(), res => { 186 | console.log(res); 187 | return res == 0; 188 | }); 189 | 190 | broker.start(); 191 | -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-mongo/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module "moleculer-db-adapter-mongo" { 2 | import { ServiceBroker, Service } from "moleculer"; 3 | 4 | import { CountOptions, CursorOptions, DbAdapter, FilterOptions, QueryOptions } from "moleculer-db"; 5 | import { Collection } from "mongodb"; 6 | export default class MongoDbAdapter implements DbAdapter { 7 | collection: Collection; 8 | constructor(uri: string, opts?: object | string, dbName?: string); 9 | 10 | /** 11 | * Initialize adapter 12 | * 13 | * @param {ServiceBroker} broker 14 | * @param {Service} service 15 | * @memberof DbAdapter 16 | */ 17 | init(broker: ServiceBroker, service: Service): void; 18 | /** 19 | * Connect to database 20 | * 21 | * @returns {Promise} 22 | * @memberof DbAdapter 23 | */ 24 | connect(): Promise; 25 | /** 26 | * Disconnect from database 27 | * 28 | * @returns {Promise} 29 | * @memberof DbAdapter 30 | */ 31 | disconnect(): Promise; 32 | 33 | /** 34 | * Find all entities by filters. 35 | * 36 | * Available filter props: 37 | * - limit 38 | * - offset 39 | * - sort 40 | * - search 41 | * - searchFields 42 | * - query 43 | * 44 | * @param {Object} filters 45 | * @returns {Promise} 46 | * @memberof DbAdapter 47 | */ 48 | find(filters: FilterOptions): Promise; 49 | 50 | /** 51 | * Find an entity by query 52 | * 53 | * @param {Object} query 54 | * @returns {Promise} 55 | * @memberof DbAdapter 56 | */ 57 | findOne(query: Q): Promise; 58 | 59 | /** 60 | * Find an entity by ID 61 | * 62 | * @param {any} id 63 | * @returns {Promise} 64 | * @memberof DbAdapter 65 | */ 66 | findById(id: unknown): Promise; 67 | 68 | /** 69 | * Find all entites by IDs 70 | * 71 | * @param {Array} ids 72 | * @returns {Promise} 73 | * @memberof DbAdapter 74 | */ 75 | findByIds(ids: (string | number)[]): Promise; 76 | 77 | /** 78 | * Get count of filtered entites 79 | * 80 | * Available filter props: 81 | * - search 82 | * - searchFields 83 | * - query 84 | * 85 | * @param {Object} [filters={}] 86 | * @returns {Promise} 87 | * @memberof DbAdapter 88 | */ 89 | count(filters?: CountOptions): Promise; 90 | 91 | /** 92 | * Insert an entity 93 | * 94 | * @param {Object} entity 95 | * @returns {Promise} 96 | * @memberof MemoryDbAdapter 97 | */ 98 | insert(entity: object): Promise; 99 | 100 | /** 101 | * Insert multiple entities 102 | * 103 | * @param {Array} entities 104 | * @returns {Promise} 105 | * @memberof MemoryDbAdapter 106 | */ 107 | insertMany(...entities: object[]): Promise; 108 | 109 | /** 110 | * Update many entities by `query` and `update` 111 | * 112 | * @param {Object} query 113 | * @param {Object} update 114 | * @returns {Promise} 115 | * @memberof DbAdapter 116 | */ 117 | updateMany(query: Q, update: object): Promise; 118 | 119 | /** 120 | * Update an entity by ID 121 | * 122 | * @param {string|number} id 123 | * @param {Object} update 124 | * @returns {Promise} 125 | * @memberof DbAdapter 126 | */ 127 | updateById(id: string | number, update: object): Promise; 128 | 129 | /** 130 | * Remove many entities which are matched by `query` 131 | * 132 | * @param {Object} query 133 | * @returns {Promise} 134 | * @memberof DbAdapter 135 | */ 136 | removeMany(query: QueryOptions): Promise; 137 | 138 | /** 139 | * Remove an entity by ID 140 | * 141 | * @param {number|string} id 142 | * @returns {Promise} 143 | * @memberof DbAdapter 144 | */ 145 | removeById(id: number | string): Promise; 146 | 147 | /** 148 | * Clear all entities from DB 149 | * 150 | * @returns {Promise} 151 | * @memberof DbAdapter 152 | */ 153 | clear(): Promise; 154 | 155 | /** 156 | * Convert DB entity to JSON object 157 | * 158 | * @param {any} entity 159 | * @returns {Object} 160 | * @memberof DbAdapter 161 | */ 162 | entityToObject(entity: unknown): object; 163 | 164 | /** 165 | * Add filters to query 166 | * 167 | * Available filters: 168 | * - search 169 | * - searchFields 170 | * - sort 171 | * - limit 172 | * - offset 173 | * - query 174 | * 175 | * @param {Object} params 176 | * @returns {any} 177 | * @memberof DbAdapter 178 | */ 179 | createCursor(params: CursorOptions): any; 180 | 181 | /** 182 | * Transforms 'idField' into NeDB's '_id' 183 | * @param {Object} entity 184 | * @param {String} idField 185 | * @memberof DbAdapter 186 | * @returns {Object} Modified entity 187 | */ 188 | beforeSaveTransformID(entity: object, idField: string): object; 189 | 190 | /** 191 | * Transforms NeDB's '_id' into user defined 'idField' 192 | * @param {Object} entity 193 | * @param {String} idField 194 | * @memberof DbAdapter 195 | * @returns {Object} Modified entity 196 | */ 197 | afterRetrieveTransformID(entity: object, idField: string): object; 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-mongo/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer-db-adapter-mongo 3 | * Copyright (c) 2019 MoleculerJS (https://github.com/moleculerjs/moleculer-db) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | module.exports = require("./src"); 10 | -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-mongo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "moleculer-db-adapter-mongo", 3 | "version": "0.4.21", 4 | "description": "MongoDB native adapter for Moleculer DB service.", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "nodemon examples/index.js", 8 | "ci": "jest --watch", 9 | "test": "jest --coverage", 10 | "lint": "eslint --ext=.js src test", 11 | "deps": "npm-check -u", 12 | "readme": "node node_modules/moleculer-docgen/index.js -t README.md src/index.js", 13 | "coverall": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js" 14 | }, 15 | "keywords": [ 16 | "microservice", 17 | "moleculer" 18 | ], 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/moleculerjs/moleculer-db.git" 22 | }, 23 | "homepage": "https://github.com/moleculerjs/moleculer-db/tree/master/packages/moleculer-db-adapter-mongo#readme", 24 | "author": "MoleculerJS", 25 | "license": "MIT", 26 | "peerDependencies": { 27 | "moleculer": "^0.12.0 || ^0.13.0 || ^0.14.0 || ^0.15.0" 28 | }, 29 | "devDependencies": { 30 | "benchmarkify": "^3.0.0", 31 | "coveralls": "^3.1.1", 32 | "eslint": "^8.21.0", 33 | "jest": "^27.2.5", 34 | "jest-cli": "^27.2.5", 35 | "lolex": "^6.0.0", 36 | "moleculer": "^0.14.22", 37 | "moleculer-docgen": "^0.3.0", 38 | "nodemon": "^2.0.19", 39 | "npm-check": "^5.9.2" 40 | }, 41 | "jest": { 42 | "testEnvironment": "node", 43 | "coveragePathIgnorePatterns": [ 44 | "/node_modules/", 45 | "/test/services/" 46 | ] 47 | }, 48 | "engines": { 49 | "node": ">= 8.x.x" 50 | }, 51 | "dependencies": { 52 | "fakerator": "^0.3.6", 53 | "mongodb": "^3.6.10" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-mongoose/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | "env": { 4 | "node": true, 5 | "commonjs": true, 6 | "es6": true, 7 | "jquery": false, 8 | "jest": true, 9 | "jasmine": true 10 | }, 11 | "extends": "eslint:recommended", 12 | "parserOptions": { 13 | "sourceType": "module", 14 | "ecmaVersion": "2022" 15 | }, 16 | "rules": { 17 | "indent": [ 18 | "warn", 19 | "tab", 20 | { SwitchCase: 1 } 21 | ], 22 | "quotes": [ 23 | "warn", 24 | "double" 25 | ], 26 | "semi": [ 27 | "error", 28 | "always" 29 | ], 30 | "no-var": [ 31 | "error" 32 | ], 33 | "no-console": [ 34 | "off" 35 | ], 36 | "no-unused-vars": [ 37 | "warn" 38 | ] 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-mongoose/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | coverage/ 4 | npm-debug.log 5 | stats.json 6 | yarn-error.log -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-mongoose/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | # 0.9.3 (2023-07-15) 3 | 4 | ## Changes 5 | - support virtual field population in mongoose adapter [#354](https://github.com/moleculerjs/moleculer-db/pull/354) 6 | 7 | -------------------------------------------------- 8 | 9 | # 0.9.2 (2023-02-11) 10 | 11 | ## Changes 12 | - fix constructor signature of MongoDB adapter in TypeScript [#346](https://github.com/moleculerjs/moleculer-db/pull/346) 13 | - update mongoose to 6.9.0 [#347](https://github.com/moleculerjs/moleculer-db/pull/347) 14 | 15 | -------------------------------------------------- 16 | 17 | # 0.9.0 (2022-10-23) 18 | 19 | ## Changes 20 | support mongoose@6.x.x by @devalexandre in [#324](https://github.com/moleculerjs/moleculer-db/pull/324) 21 | 22 | -------------------------------------------------- 23 | 24 | # 0.8.9 (2020-11-16) 25 | 26 | ## Changes 27 | fix mongoose stuck after `broker.stop` [#233](https://github.com/moleculerjs/moleculer-db/pull/233) 28 | 29 | -------------------------------------------------- 30 | 31 | # 0.8.8 (2020-09-27) 32 | 33 | ## Changes 34 | fix connection error if there is more than one service [#222](https://github.com/moleculerjs/moleculer-db/pull/222) 35 | 36 | -------------------------------------------------- 37 | 38 | # 0.8.6 (2020-06-18) 39 | 40 | ## Changes 41 | Add support for searchFields parameter [#197](https://github.com/moleculerjs/moleculer-db/pull/197) 42 | 43 | -------------------------------------------------- 44 | 45 | # 0.8.0 (2019-07-07) 46 | 47 | ## Changes 48 | Mongoose connection logic has been changed. No need to update existing codes. 49 | 50 | -------------------------------------------------- 51 | 52 | # 0.7.0 (2018-04-08) 53 | 54 | ## Breaking changes 55 | Dependency `mongoose` moved to peer dependencies. It means you should install `mongoose` in your project. 56 | 57 | **New install script** 58 | ```bash 59 | $ npm install moleculer-db moleculer-db-adapter-mongoose mongoose --save 60 | ``` 61 | 62 | -------------------------------------------------- 63 | 64 | # 0.6.0 (2018-03-28) 65 | 66 | ## Breaking changes 67 | - Update mongoose to v5.0.12 68 | - Changed constructor signature 69 | 70 | **Example with connection URI** 71 | ```js 72 | new MongooseAdapter("mongodb://127.0.0.1/moleculer-db") 73 | ``` 74 | 75 | **Example with URI and options** 76 | ```js 77 | new MongooseAdapter("mongodb://db-server-hostname/my-db", { 78 | user: process.env.MONGO_USERNAME, 79 | pass: process.env.MONGO_PASSWORD 80 | keepAlive: true 81 | }) 82 | ``` 83 | 84 | -------------------------------------------------- 85 | 86 | # 0.2.0 (2017-07-06) 87 | 88 | ## Breaking changes 89 | 90 | ### Update methods to moleculer-db v0.2.0 91 | - `findAll` renamed to `find` 92 | - `update` renamed to `updateMany` 93 | - `remove` renamed to `removeMany` 94 | 95 | -------------------------------------------------- 96 | -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-mongoose/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 MoleculerJS 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 | -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-mongoose/README.md: -------------------------------------------------------------------------------- 1 | ![Moleculer logo](http://moleculer.services/images/banner.png) 2 | 3 | # moleculer-db-adapter-mongoose [![NPM version](https://img.shields.io/npm/v/moleculer-db-adapter-mongoose.svg)](https://www.npmjs.com/package/moleculer-db-adapter-mongoose) 4 | 5 | Mongoose adapter for Moleculer DB service 6 | 7 | ## Features 8 | 9 | ## Install 10 | 11 | ```bash 12 | $ npm install moleculer-db moleculer-db-adapter-mongoose mongoose@6.5.4 --save 13 | ``` 14 | 15 | ## Usage 16 | 17 | ```js 18 | "use strict"; 19 | 20 | const { ServiceBroker } = require("moleculer"); 21 | const DbService = require("moleculer-db"); 22 | const MongooseAdapter = require("moleculer-db-adapter-mongoose"); 23 | const mongoose = require("mongoose"); 24 | 25 | const broker = new ServiceBroker(); 26 | 27 | // Create a Mongoose service for `post` entities 28 | broker.createService({ 29 | name: "posts", 30 | mixins: [DbService], 31 | adapter: new MongooseAdapter("mongodb://127.0.0.1/moleculer-demo"), 32 | model: mongoose.model("Post", mongoose.Schema({ 33 | title: { type: String }, 34 | content: { type: String }, 35 | votes: { type: Number, default: 0} 36 | })) 37 | }); 38 | 39 | 40 | broker.start() 41 | // Create a new post 42 | .then(() => broker.call("posts.create", { 43 | title: "My first post", 44 | content: "Lorem ipsum...", 45 | votes: 0 46 | })) 47 | 48 | // Get all posts 49 | .then(() => broker.call("posts.find").then(console.log)); 50 | ``` 51 | 52 | ## Options 53 | 54 | **Example with connection URI** 55 | ```js 56 | new MongooseAdapter("mongodb://127.0.0.1/moleculer-db") 57 | ``` 58 | 59 | **Example with URI and options** 60 | ```js 61 | new MongooseAdapter("mongodb://db-server-hostname/my-db", { 62 | user: process.env.MONGO_USERNAME, 63 | pass: process.env.MONGO_PASSWORD, 64 | keepAlive: true 65 | }) 66 | ``` 67 | 68 | # Test 69 | ``` 70 | $ npm test 71 | ``` 72 | 73 | In development with watching 74 | 75 | ``` 76 | $ npm run ci 77 | ``` 78 | 79 | # License 80 | The project is available under the [MIT license](https://tldrlegal.com/license/mit-license). 81 | 82 | # Contact 83 | Copyright (c) 2016-2024 MoleculerJS 84 | 85 | [![@moleculerjs](https://img.shields.io/badge/github-moleculerjs-green.svg)](https://github.com/moleculerjs) [![@MoleculerJS](https://img.shields.io/badge/twitter-MoleculerJS-blue.svg)](https://twitter.com/MoleculerJS) 86 | -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-mongoose/examples/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const moduleName = process.argv[2] || "simple"; 4 | process.argv.splice(2, 1); 5 | 6 | require("./" + moduleName); -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-mongoose/examples/integration/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { ServiceBroker } = require("moleculer"); 4 | const StoreService = require("../../../moleculer-db/index"); 5 | const MongooseAdapter = require("../../index"); 6 | const ModuleChecker = require("../../../moleculer-db/test/checker"); 7 | const Post = require("../models/posts"); 8 | 9 | // Create broker 10 | const broker = new ServiceBroker({ 11 | logger: console, 12 | logLevel: "debug" 13 | }); 14 | 15 | // Load my service 16 | broker.createService(StoreService, { 17 | name: "posts", 18 | adapter: new MongooseAdapter("mongodb://127.0.0.1/moleculer-db-demo"), 19 | model: Post, 20 | settings: { 21 | fields: ["_id", "title", "content", "votes", "status", "updatedAt"] 22 | }, 23 | 24 | actions: { 25 | vote(ctx) { 26 | return this.Promise.resolve(ctx) 27 | .then(ctx => this.adapter.updateById(ctx.params.id, { $inc: { votes: 1 } })); 28 | }, 29 | 30 | unvote(ctx) { 31 | return this.Promise.resolve(ctx) 32 | .then(ctx => this.adapter.updateById(ctx.params.id, { $inc: { votes: -1 } })); 33 | } 34 | }, 35 | 36 | afterConnected() { 37 | return this.adapter.clear(); 38 | } 39 | }); 40 | 41 | const checker = new ModuleChecker(11); 42 | 43 | // Start checks 44 | function start() { 45 | broker.start() 46 | .delay(500) 47 | .then(() => checker.execute()) 48 | .catch(console.error) 49 | .then(() => broker.stop()) 50 | .then(() => checker.printTotal()); 51 | } 52 | 53 | // --- TEST CASES --- 54 | 55 | let id =[]; 56 | 57 | // Count of posts 58 | checker.add("COUNT", () => broker.call("posts.count"), res => { 59 | console.log(res); 60 | return res == 0; 61 | }); 62 | 63 | // Create new Posts 64 | checker.add("--- CREATE ---", () => broker.call("posts.create", { title: "Hello", content: "Post content", votes: 2, status: true }), doc => { 65 | id = doc._id; 66 | console.log("Saved: ", doc); 67 | return doc._id && doc.title === "Hello" && doc.content === "Post content" && doc.votes === 2 && doc.status === true; 68 | }); 69 | 70 | // List posts 71 | checker.add("--- FIND ---", () => broker.call("posts.find"), res => { 72 | console.log(res); 73 | return res.length == 1 && res[0]._id == id; 74 | }); 75 | 76 | // Get a post 77 | checker.add("--- GET ---", () => broker.call("posts.get", { id }), res => { 78 | console.log(res); 79 | return res._id == id; 80 | }); 81 | 82 | // Vote a post 83 | checker.add("--- VOTE ---", () => broker.call("posts.vote", { 84 | id 85 | }), res => { 86 | console.log(res); 87 | return res._id == id && res.votes === 3; 88 | }); 89 | 90 | // Update a posts 91 | checker.add("--- UPDATE ---", () => broker.call("posts.update", { 92 | id, 93 | title: "Hello 2", 94 | content: "Post content 2", 95 | updatedAt: new Date() 96 | }), doc => { 97 | console.log(doc); 98 | return doc._id && doc.title === "Hello 2" && doc.content === "Post content 2" && doc.votes === 3 && doc.status === true && doc.updatedAt; 99 | }); 100 | 101 | // Get a post 102 | checker.add("--- GET ---", () => broker.call("posts.get", { id }), doc => { 103 | console.log(doc); 104 | return doc._id == id && doc.title == "Hello 2" && doc.votes === 3; 105 | }); 106 | 107 | // Unvote a post 108 | checker.add("--- UNVOTE ---", () => broker.call("posts.unvote", { 109 | id 110 | }), res => { 111 | console.log(res); 112 | return res._id == id && res.votes === 2; 113 | }); 114 | 115 | // Count of posts 116 | checker.add("--- COUNT ---", () => broker.call("posts.count"), res => { 117 | console.log(res); 118 | return res == 1; 119 | }); 120 | 121 | // Remove a post 122 | checker.add("--- REMOVE ---", () => broker.call("posts.remove", { id }), res => { 123 | console.log(res); 124 | return res._id == id; 125 | }); 126 | 127 | // Count of posts 128 | checker.add("--- COUNT ---", () => broker.call("posts.count"), res => { 129 | console.log(res); 130 | return res == 0; 131 | }); 132 | 133 | 134 | start(); 135 | -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-mongoose/examples/models/posts.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const mongoose = require("mongoose"); 4 | const Schema = mongoose.Schema; 5 | 6 | const PostSchema = new Schema({ 7 | title: { 8 | type: String, 9 | trim: true 10 | }, 11 | content: { 12 | type: String, 13 | trim: true 14 | }, 15 | votes: { 16 | type: Number, 17 | default: 0 18 | }, 19 | author: { 20 | type: Schema.ObjectId 21 | }, 22 | status: { 23 | type: Boolean, 24 | default: true 25 | } 26 | 27 | }, { 28 | timestamps: true 29 | }); 30 | 31 | // Add full-text search index 32 | PostSchema.index({ 33 | //"$**": "text" 34 | "title": "text", 35 | "content": "text" 36 | }); 37 | 38 | module.exports = mongoose.model("Post", PostSchema); 39 | -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-mongoose/examples/models/users.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const mongoose = require("mongoose"); 4 | const Schema = mongoose.Schema; 5 | 6 | const UserSchema = new Schema({ 7 | username: { 8 | type: String, 9 | trim: true 10 | }, 11 | fullName: { 12 | type: String, 13 | trim: true 14 | }, 15 | email: { 16 | type: String, 17 | trim: true 18 | }, 19 | status: { 20 | type: Number, 21 | default: 1 22 | } 23 | 24 | }, { 25 | timestamps: true 26 | }); 27 | 28 | // Add full-text search index 29 | UserSchema.index({ 30 | "fullName": "text" 31 | }); 32 | 33 | module.exports =mongoose.model("User", UserSchema); 34 | -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-mongoose/examples/populates/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { ServiceBroker } = require("moleculer"); 4 | const StoreService = require("../../../moleculer-db/index"); 5 | const MongooseAdapter = require("../../index"); 6 | const ModuleChecker = require("../../../moleculer-db/test/checker"); 7 | const Post = require("../models/posts"); 8 | const User = require("../models/users"); 9 | 10 | // Create broker 11 | const broker = new ServiceBroker({ 12 | logger: console, 13 | logLevel: "debug" 14 | }); 15 | 16 | let posts = []; 17 | let users = []; 18 | 19 | // Load my service 20 | broker.createService(StoreService, { 21 | name: "posts", 22 | adapter: new MongooseAdapter("mongodb://127.0.0.1/moleculer-db-demo"), 23 | //dependencies: ["users"], 24 | model: Post, 25 | settings: { 26 | fields: ["_id", "title", "content", "votes", "author"], 27 | 28 | populates: { 29 | "author": { 30 | action: "users.get", 31 | params: { 32 | fields: ["username", "fullName"] 33 | } 34 | } 35 | } 36 | }, 37 | 38 | afterConnected() { 39 | return this.adapter.clear().delay(3000).then(() => { 40 | if (users.length == 0) return; 41 | 42 | this.logger.info("Seed Posts collection..."); 43 | return this.adapter.insertMany([ 44 | { title: "1st post", content: "First post content.", votes: 3, author: users[2]._id }, 45 | { title: "2nd post", content: "Labore eum veritatis ut.", votes: 8, author: users[1]._id }, 46 | { title: "3rd post", content: "Rerum deleniti repellendus error ea.", votes: 0, author: users[4]._id }, 47 | { title: "4th post", content: "Normal post content.", votes: 4, author: users[3]._id }, 48 | { title: "5th post", content: "Voluptatum praesentium voluptatibus est nesciunt fugiat.", votes: 6, author: users[1]._id } 49 | ]).then(docs => posts = docs); 50 | }); 51 | } 52 | }); 53 | 54 | // Load my service 55 | broker.createService(StoreService, { 56 | name: "users", 57 | adapter: new MongooseAdapter("mongodb://127.0.0.1/moleculer-db-demo"), 58 | model: User, 59 | settings: { 60 | fields: ["_id", "username", "fullName", "email"] 61 | }, 62 | 63 | afterConnected() { 64 | return this.adapter.clear().then(() => { 65 | this.logger.info("Seed Users collection..."); 66 | return this.adapter.insertMany([ 67 | { username: "John", fullName: "John Doe", email: "john.doe@gmail.com", status: 1 }, 68 | { username: "Adam", fullName: "Adam Doe", email: "adam.doe@gmail.com", status: 1 }, 69 | { username: "Jane", fullName: "Jane Doe", email: "jane.doe@gmail.com", status: 0 }, 70 | { username: "Susan", fullName: "Susan Doe", email: "susan.doe@gmail.com", status: 1 }, 71 | { username: "Bill", fullName: "Bill Doe", email: "bill.doe@gmail.com", status: 1 } 72 | ]).then(docs => { 73 | users = docs; 74 | }); 75 | }); 76 | } 77 | }); 78 | 79 | const checker = new ModuleChecker(13); 80 | 81 | // Start checks 82 | function start() { 83 | broker.start() 84 | .delay(1000) 85 | .then(() => checker.execute()) 86 | .catch(console.error) 87 | .then(() => broker.stop()) 88 | .then(() => checker.printTotal()); 89 | } 90 | 91 | // --- TEST CASES --- 92 | 93 | checker.add("FIND POSTS (search: 'content')", () => broker.call("posts.find", { limit: 0, offset: 0, sort: "-votes title", search: "content", populate: ["author"], fields: ["_id", "title", "author"] }), res => { 94 | console.log(res); 95 | return [ 96 | res.length == 2 && res[0]._id == posts[3]._id && res[1]._id == posts[0]._id, 97 | res[0].title && res[0].author && res[0].author.username == "Susan" && res[0].votes == null && res[0].author.email == null, 98 | res[1].title && res[1].author && res[1].author.username == "Jane" && res[1].votes == null && res[1].author.email == null 99 | ]; 100 | }); 101 | 102 | checker.add("COUNT POSTS (search: 'content')", () => broker.call("posts.count", { search: "content" }), res => { 103 | console.log(res); 104 | return res == 2; 105 | }); 106 | 107 | checker.add("FIND POSTS (limit: 3, offset: 2, sort: title, no author)", () => broker.call("posts.find", { limit: 3, offset: 2, sort: "title", fields: ["title", "votes"] }), res => { 108 | console.log(res); 109 | return [ 110 | res.length == 3, 111 | res[0].title && res[0].author == null && res[0].votes == 0, 112 | res[1].title && res[1].author == null && res[1].votes == 4, 113 | res[2].title && res[2].author == null && res[2].votes == 6 114 | ]; 115 | }); 116 | 117 | checker.add("GET POST (page: 2, pageSize: 5, sort: -votes)", () => broker.call("posts.get", { id: posts[2]._id.toString(), populate: ["author"], fields: ["title", "author"] }), res => { 118 | console.log(res); 119 | return res.title === "3rd post" && res.author.username === "Bill" && res.author.fullName === "Bill Doe" && res.author.email == null && res.votes == null; 120 | }); 121 | 122 | checker.add("LIST POSTS (page: 2, pageSize: 5, sort: -votes)", () => broker.call("posts.list", { page: 2, pageSize: 2, sort: "-votes", populate: ["author"], fields: ["_id", "title", "votes", "author"] }), res => { 123 | console.log(res); 124 | let rows = res.rows; 125 | return [ 126 | res.total === 5 && res.page === 2 && res.pageSize === 2 && res.totalPages === 3, 127 | rows.length == 2, 128 | rows[0]._id == posts[3]._id && rows[0].title && rows[0].author.username == "Susan" && rows[0].votes == 4, 129 | rows[1]._id == posts[0]._id && rows[1].title && rows[1].author.username == "Jane" && rows[1].votes == 3 130 | ]; 131 | }); 132 | 133 | start(); 134 | -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-mongoose/examples/search/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { ServiceBroker } = require("moleculer"); 4 | const DbService = require("../../../moleculer-db/index"); 5 | const MongooseDBAdapter = require("../../index"); 6 | const Fakerator = require("fakerator")(); 7 | const User = require("../models/users"); 8 | 9 | // Create broker 10 | let broker = new ServiceBroker({ 11 | logger: console, 12 | logLevel: "debug" 13 | }); 14 | 15 | // Load my service 16 | broker.createService({ 17 | name: "users", 18 | mixins: [DbService], 19 | adapter: new MongooseDBAdapter("mongodb://127.0.0.1/search2-test"), 20 | model: User, 21 | 22 | methods: { 23 | seed() { 24 | Fakerator.seed(12345); 25 | return this.adapter.insertMany(Array(100).fill(0).map(() => { 26 | return { 27 | username: Fakerator.internet.userName(), 28 | fullName: Fakerator.names.name(), 29 | email: Fakerator.internet.email(), 30 | status: Fakerator.random.number(0,1) 31 | }; 32 | })); 33 | }, 34 | }, 35 | 36 | /** 37 | * Actions 38 | */ 39 | actions: { 40 | 41 | /** 42 | * Perform a textual search with 'find' method 43 | */ 44 | searchWithFind(ctx) { 45 | return ctx.call("users.find", { 46 | search: "Dianne", 47 | searchFields: ["name"] 48 | }); 49 | }, 50 | 51 | /** 52 | * Perform a textual search with 'list' method 53 | */ 54 | searchWithList(ctx) { 55 | return ctx.call("users.list", { 56 | search: "Dianne", 57 | //searchFields: ["name"] 58 | }); 59 | } 60 | 61 | }, 62 | 63 | afterConnected() { 64 | return this.adapter.clear() 65 | .then(() => this.seed()); 66 | 67 | } 68 | }); 69 | 70 | broker.start() 71 | .then(() => broker.repl()) 72 | .then(() => { 73 | return broker.call("users.list", { 74 | search: "Eric" 75 | }).then(res => console.log(res)); 76 | }) 77 | .catch(err => broker.logger.error(err)); 78 | -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-mongoose/examples/simple/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | let { ServiceBroker } = require("moleculer"); 4 | let StoreService = require("../../../moleculer-db/index"); 5 | let ModuleChecker = require("../../../moleculer-db/test/checker"); 6 | let MongooseAdapter = require("../../index"); 7 | let Post = require("../models/posts"); 8 | 9 | // Create broker 10 | let broker = new ServiceBroker({ 11 | logger: console, 12 | logLevel: "debug" 13 | }); 14 | let adapter; 15 | 16 | // Load my service 17 | broker.createService(StoreService, { 18 | name: "posts", 19 | adapter: new MongooseAdapter("mongodb://127.0.0.1/moleculer-db-demo",{ 20 | useNewUrlParser: true, 21 | useUnifiedTopology: true 22 | }), 23 | model: Post, 24 | settings: {}, 25 | 26 | afterConnected() { 27 | adapter = this.adapter; 28 | return this.adapter.clear(); 29 | } 30 | }); 31 | 32 | const checker = new ModuleChecker(24); 33 | 34 | // Start checks 35 | function start() { 36 | broker.start() 37 | .delay(500) 38 | .then(() => checker.execute()) 39 | .catch(console.error) 40 | .then(() => broker.stop()) 41 | .then(() => checker.printTotal()); 42 | } 43 | 44 | // --- TEST CASES --- 45 | 46 | let ids =[]; 47 | let date = new Date(); 48 | 49 | // Count of posts 50 | checker.add("COUNT", () => adapter.count(), res => { 51 | console.log(res); 52 | return res == 0; 53 | }); 54 | 55 | // Insert a new Post 56 | checker.add("INSERT", () => adapter.insert({ title: "Hello", content: "Post content", votes: 3, status: true, createdAt: date }), doc => { 57 | ids[0] = doc._id.toHexString(); 58 | console.log("Saved: ", doc); 59 | return doc._id && doc.title === "Hello" && doc.content === "Post content" && doc.votes === 3 && doc.status === true && doc.createdAt === date; 60 | }); 61 | 62 | // Find 63 | checker.add("FIND", () => adapter.find({}), res => { 64 | console.log(res); 65 | return res.length == 1 && res[0]._id.toHexString() == ids[0]; 66 | }); 67 | 68 | // Find by ID 69 | checker.add("GET", () => adapter.findById(ids[0]), res => { 70 | console.log(res); 71 | return res._id.toHexString() == ids[0]; 72 | }); 73 | 74 | // Count of posts 75 | checker.add("COUNT", () => adapter.count(), res => { 76 | console.log(res); 77 | return res == 1; 78 | }); 79 | 80 | // Insert many new Posts 81 | checker.add("INSERT MANY", () => adapter.insertMany([ 82 | { title: "Second", content: "Second post content", votes: 8, status: true, createdAt: new Date() }, 83 | { title: "Last", content: "Last document", votes: 1, status: false, createdAt: new Date() } 84 | ]), docs => { 85 | console.log("Saved: ", docs); 86 | ids[1] = docs[0]._id.toHexString(); 87 | ids[2] = docs[1]._id.toHexString(); 88 | 89 | return [ 90 | docs.length == 2, 91 | ids[1] && docs[0].title === "Second" && docs[0].votes === 8, 92 | ids[1] && docs[1].title === "Last" && docs[1].votes === 1 && docs[1].status === false 93 | ]; 94 | }); 95 | 96 | // Count of posts 97 | checker.add("COUNT", () => adapter.count(), res => { 98 | console.log(res); 99 | return res == 3; 100 | }); 101 | 102 | // Find 103 | checker.add("FIND by query", () => adapter.find({ query: { title: "Last" } }), res => { 104 | console.log(res); 105 | return res.length == 1 && res[0]._id.toHexString() == ids[2]; 106 | }); 107 | 108 | // Find 109 | checker.add("FIND by limit, sort, query", () => adapter.find({ limit: 1, sort: ["votes", "-title"], offset: 1 }), res => { 110 | console.log(res); 111 | return res.length == 1 && res[0]._id.toHexString() == ids[0]; 112 | }); 113 | 114 | // Find 115 | checker.add("FIND by query ($gt)", () => adapter.find({ query: { votes: { $gt: 2 } } }), res => { 116 | console.log(res); 117 | return res.length == 2; 118 | }); 119 | 120 | // Find 121 | checker.add("COUNT by query ($gt)", () => adapter.count({ query: { votes: { $gt: 2 } } }), res => { 122 | console.log(res); 123 | return res == 2; 124 | }); 125 | 126 | // Find 127 | checker.add("FIND by text search", () => adapter.find({ search: "content" }), res => { 128 | console.log(res); 129 | return [ 130 | res.length == 2, 131 | res[0]._doc._score < 1 && res[0].title === "Hello", 132 | res[1]._doc._score < 1 && res[1].title === "Second" 133 | ]; 134 | }); 135 | 136 | // Find by IDs 137 | checker.add("GET BY IDS", () => adapter.findByIds([ids[2], ids[0]]), res => { 138 | console.log(res); 139 | return res.length == 2; 140 | }); 141 | 142 | // Update a posts 143 | checker.add("UPDATE", () => adapter.updateById(ids[2], { $set: { 144 | title: "Last 2", 145 | updatedAt: new Date(), 146 | status: true 147 | }}), doc => { 148 | console.log("Updated: ", doc); 149 | return doc._id && doc.title === "Last 2" && doc.content === "Last document" && doc.votes === 1 && doc.status === true && doc.updatedAt; 150 | }); 151 | 152 | // Update by query 153 | checker.add("UPDATE BY QUERY", () => adapter.updateMany({ votes: { $lt: 5 }}, { 154 | $set: { status: false } 155 | }), count => { 156 | console.log("Updated: ", count); 157 | return count == 2; 158 | }); 159 | 160 | // Remove by query 161 | checker.add("REMOVE BY QUERY", () => adapter.removeMany({ votes: { $lt: 5 }}), count => { 162 | console.log("Removed: ", count); 163 | return count == 2; 164 | }); 165 | 166 | // Count of posts 167 | checker.add("COUNT", () => adapter.count(), res => { 168 | console.log(res); 169 | return res == 1; 170 | }); 171 | 172 | // Remove by ID 173 | checker.add("REMOVE BY ID", () => adapter.removeById(ids[1]), doc => { 174 | console.log("Removed: ", doc); 175 | return doc && doc._id.toHexString() == ids[1]; 176 | }); 177 | 178 | // Count of posts 179 | checker.add("COUNT", () => adapter.count(), res => { 180 | console.log(res); 181 | return res == 0; 182 | }); 183 | 184 | // Clear 185 | checker.add("CLEAR", () => adapter.clear(), res => { 186 | console.log(res); 187 | return res == 0; 188 | }); 189 | 190 | start(); 191 | -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-mongoose/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module "moleculer-db-adapter-mongoose" { 2 | import { Service, ServiceBroker } from "moleculer"; 3 | import { 4 | ConnectOptions , 5 | Document, 6 | Query as DocumentQuery, //reff: https://github.com/Automattic/mongoose/issues/10036#issuecomment-803144616 7 | Model, 8 | Schema, 9 | } from "mongoose"; 10 | import { Db } from "mongodb"; 11 | 12 | type HasModelOrSchema = 13 | | { 14 | model: Model; 15 | } 16 | | { 17 | schema: Schema; 18 | modelName: string; 19 | }; 20 | 21 | /** 22 | * Parameters to {@link MongooseDbAdapter.count} 23 | */ 24 | interface CountFilters { 25 | query?: any; 26 | search?: string; 27 | searchFields?: string[]; // never used? 28 | } 29 | 30 | 31 | 32 | /** 33 | * Parameters to {@link MongooseDbAdapter.createCursor} 34 | */ 35 | interface FindFilters { 36 | query?: any; 37 | search?: string; 38 | searchFields?: string[]; // never used??? 39 | sort?: string | string[]; 40 | offset?: number; 41 | limit?: number; 42 | } 43 | 44 | class MongooseDbAdapter { 45 | uri: string; 46 | opts?: ConnectOptions; 47 | broker: ServiceBroker; 48 | service: Service; 49 | model: Model; 50 | schema?: Schema; 51 | modelName?: string; 52 | db: Db; 53 | 54 | /** 55 | * Creates an instance of MongooseDbAdapter. 56 | */ 57 | constructor(uri: string, opts?: ConnectOptions); 58 | /** 59 | * Initialize adapter 60 | */ 61 | init( 62 | broker: ServiceBroker, 63 | service: Service & HasModelOrSchema 64 | ): void; 65 | /** 66 | * Connect to database 67 | */ 68 | connect(): Promise; 69 | /** 70 | * Disconnect from database 71 | */ 72 | disconnect(): Promise; 73 | /** 74 | * Find all entities by filters. 75 | * 76 | * Available filter props: 77 | * - limit 78 | * - offset 79 | * - sort 80 | * - search 81 | * - searchFields 82 | * - query 83 | */ 84 | find(filters: FindFilters): Promise; 85 | /** 86 | * Find an entity by query 87 | */ 88 | findOne(query: any): Promise; 89 | /** 90 | * Find an entities by ID 91 | */ 92 | findById(_id: any): Promise; 93 | /** 94 | * Find any entities by IDs 95 | */ 96 | findByIds(idList: any[]): Promise; 97 | /** 98 | * Get count of filtered entites 99 | * 100 | * Available filter props: 101 | * - search 102 | * - searchFields 103 | * - query 104 | */ 105 | count(filters?: CountFilters): Promise; 106 | /** 107 | * Insert an entity 108 | */ 109 | insert(entity: any): Promise; 110 | /** 111 | * Insert many entities 112 | */ 113 | insertMany(entities: any[]): Promise; 114 | /** 115 | * Update many entities by `query` and `update` 116 | */ 117 | updateMany(query: any, update: any): Promise; 118 | /** 119 | * Update an entity by ID and `update` 120 | */ 121 | updateById( 122 | _id: any, 123 | update: any 124 | ): DocumentQuery; 125 | /** 126 | * Remove entities which are matched by `query` 127 | */ 128 | removeMany(query: any): Promise; 129 | /** 130 | * Remove an entity by ID 131 | */ 132 | removeById(_id: any): DocumentQuery; 133 | /** 134 | * Clear all entities from collection 135 | */ 136 | clear(): Promise; 137 | /** 138 | * Convert DB entity to JSON object 139 | */ 140 | entityToObject(entity: any): any; 141 | /** 142 | * Create a filtered query 143 | * Available filters in `params`: 144 | * - search 145 | * - sort 146 | * - limit 147 | * - offset 148 | * - query 149 | */ 150 | createCursor( 151 | params: FindFilters 152 | ): DocumentQuery; 153 | 154 | /** 155 | * Transforms 'idField' into MongoDB's '_id' 156 | */ 157 | beforeSaveTransformID(entity: object, idField: string): object; 158 | 159 | /** 160 | * Transforms MongoDB's '_id' into user defined 'idField' 161 | */ 162 | afterRetrieveTransformID(entity: object, idField: string): object; 163 | } 164 | export = MongooseDbAdapter; 165 | } 166 | -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-mongoose/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer-db-adapter-mongoose 3 | * Copyright (c) 2019 MoleculerJS (https://github.com/moleculerjs/moleculer-db) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | module.exports = require("./src"); 10 | -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-mongoose/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "moleculer-db-adapter-mongoose", 3 | "version": "0.10.1", 4 | "description": "Mongoose adapter for Moleculer DB service", 5 | "main": "index.js", 6 | "types": "index.d.ts", 7 | "scripts": { 8 | "dev": "nodemon examples/index.js", 9 | "ci": "jest --watch", 10 | "test:unit": "jest --testMatch \"**/unit/**/*.spec.js\" --coverage", 11 | "test:integration": "jest --testMatch \"**/integration/**/*.spec.js\" --runInBand --coverage", 12 | "lint": "eslint --ext=.js src test", 13 | "deps": "npm-check -u", 14 | "coverall": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js" 15 | }, 16 | "keywords": [ 17 | "microservice", 18 | "moleculer" 19 | ], 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/moleculerjs/moleculer-db.git" 23 | }, 24 | "homepage": "https://github.com/moleculerjs/moleculer-db/tree/master/packages/moleculer-db-adapter-mongoose#readme", 25 | "author": "MoleculerJS", 26 | "license": "MIT", 27 | "peerDependencies": { 28 | "moleculer": "^0.12.0 || ^0.13.0 || ^0.14.0 || ^0.15.0", 29 | "mongoose": "^6.0.0 || ^7.0.0 || ^8.0.0" 30 | }, 31 | "devDependencies": { 32 | "benchmarkify": "^3.0.0", 33 | "coveralls": "^3.1.1", 34 | "eslint": "^8.21.0", 35 | "fakerator": "^0.3.6", 36 | "jest": "^27.2.5", 37 | "jest-cli": "^27.2.5", 38 | "lolex": "^6.0.0", 39 | "moleculer": "^0.14.22", 40 | "mongoose": "^6.13.3", 41 | "nodemon": "^2.0.19", 42 | "npm-check": "^5.9.2" 43 | }, 44 | "jest": { 45 | "testEnvironment": "node", 46 | "coveragePathIgnorePatterns": [ 47 | "/node_modules/", 48 | "/test/services/" 49 | ] 50 | }, 51 | "engines": { 52 | "node": ">= 8.x.x" 53 | }, 54 | "dependencies": { 55 | "lodash": "^4.17.21" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-mongoose/test/models/posts.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | let mongoose = require("mongoose"); 4 | let Schema = mongoose.Schema; 5 | 6 | let PostSchema = new Schema({ 7 | title: { 8 | type: String, 9 | trim: true 10 | }, 11 | content: { 12 | type: String, 13 | trim: true 14 | }, 15 | votes: { 16 | type: Number, 17 | default: 0 18 | }, 19 | author: { 20 | type: Schema.ObjectId, 21 | ref: "User" 22 | } 23 | 24 | }, { 25 | timestamps: true 26 | }); 27 | 28 | // Add full-text search index 29 | PostSchema.index({ 30 | //"$**": "text" 31 | "title": "text", 32 | "content": "text" 33 | }); 34 | 35 | module.exports = { 36 | Model: mongoose.model("Post", PostSchema), 37 | Schema: PostSchema 38 | }; 39 | -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-mongoose/test/models/users.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const {Schema, model} = require("mongoose"); 4 | 5 | const UserSchema = new Schema( 6 | { 7 | firstName: { 8 | type: String, 9 | required: true, 10 | trim: true, 11 | }, 12 | lastName: { 13 | type: String, 14 | required: true, 15 | trim: true, 16 | }, 17 | postRef: { 18 | type: String, 19 | default: "Post" 20 | } 21 | }, 22 | { 23 | timestamps: true, 24 | toJSON: { virtuals: true }, 25 | toObject: { virtuals: true }, 26 | virtuals: { 27 | fullName: { 28 | get() { 29 | return `${this.firstName} ${this.lastName}`; 30 | }, 31 | }, 32 | posts: { 33 | options: { 34 | ref: "Post", 35 | localField: "_id", 36 | foreignField: "author", 37 | options: { sort: { createdAt: -1 } }, 38 | }, 39 | }, 40 | postCount: { 41 | options: { 42 | refPath: "postRef", 43 | localField: "_id", 44 | foreignField: "author", 45 | count: true, 46 | }, 47 | }, 48 | lastPost: { 49 | options: { 50 | ref: "Post", 51 | localField: "_id", 52 | foreignField: "author", 53 | justOne: true, 54 | options: { sort: { createdAt: -1 } }, 55 | }, 56 | }, 57 | }, 58 | } 59 | ); 60 | 61 | UserSchema.virtual("lastPostWithVotes", { 62 | ref: "Post", 63 | localField: "_id", 64 | foreignField: "author", 65 | justOne: true, 66 | match: { votes: { $gt: 0 } }, 67 | options: { sort: "-createdAt" }, 68 | }); 69 | 70 | module.exports = { 71 | Model: model("User", UserSchema), 72 | Schema: UserSchema, 73 | }; 74 | -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-sequelize/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | coverage/ 4 | npm-debug.log 5 | stats.json 6 | yarn-error.log -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-sequelize/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | # 0.2.10 (2020-10-16) 3 | 4 | ## Changes 5 | - Close Sequelize before connection error. [#228](https://github.com/moleculerjs/moleculer-db/pull/228) 6 | -------------------------------------------------- 7 | 8 | # 0.2.7 (2020-06-06) 9 | 10 | ## Changes 11 | - consider `query` filters in case of full-text searching. [#153](https://github.com/moleculerjs/moleculer-db/pull/153) 12 | - use sync option from sequelize config for disable/enable sync model. [#192](https://github.com/moleculerjs/moleculer-db/pull/192) 13 | -------------------------------------------------- 14 | 15 | # 0.2.3 (2020-03-21) 16 | 17 | ## Changes 18 | - add `noSync` parameter. [#163](https://github.com/moleculerjs/moleculer-db/pull/163) 19 | 20 | -------------------------------------------------- 21 | 22 | # 0.2.2 (2020-02-02) 23 | 24 | ## Changes 25 | - add missing Bluebird dependency 26 | 27 | -------------------------------------------------- 28 | 29 | # 0.2.1 (2019-07-16) 30 | 31 | ## Changes 32 | - available to use sequelize instance as constructor parameter 33 | 34 | -------------------------------------------------- 35 | 36 | 37 | # 0.2.0 (2019-07-07) 38 | 39 | ## Breaking changes 40 | Dependency `sequelize` moved to peer dependencies. It means you should install `sequelize` in your project. 41 | 42 | **New install script** 43 | ```bash 44 | $ npm install moleculer-db moleculer-db-adapter-sequelize sequelize --save 45 | ``` 46 | -------------------------------------------------- 47 | -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-sequelize/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 MoleculerJS 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 | -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-sequelize/README.md: -------------------------------------------------------------------------------- 1 | ![Moleculer logo](http://moleculer.services/images/banner.png) 2 | 3 | # moleculer-db-adapter-sequelize [![NPM version](https://img.shields.io/npm/v/moleculer-db-adapter-sequelize.svg)](https://www.npmjs.com/package/moleculer-db-adapter-sequelize) 4 | 5 | SQL adapter (Postgres, MySQL, SQLite & MSSQL) for Moleculer DB service with [Sequelize](https://github.com/sequelize/sequelize). 6 | 7 | # Features 8 | 9 | # Install 10 | 11 | ```bash 12 | $ npm install moleculer-db-adapter-sequelize sequelize --save 13 | ``` 14 | 15 | You have to install additional packages for your database server: 16 | ```bash 17 | # For SQLite 18 | $ npm install sqlite3 --save 19 | 20 | # For MySQL 21 | $ npm install mysql2 --save 22 | 23 | # For MariaDB 24 | $ npm install mariadb --save 25 | 26 | # For PostgreSQL 27 | $ npm install pg pg-hstore --save 28 | 29 | # For MSSQL 30 | $ npm install tedious --save 31 | ``` 32 | 33 | ## Usage 34 | 35 | ```js 36 | "use strict"; 37 | 38 | const { ServiceBroker } = require("moleculer"); 39 | const DbService = require("moleculer-db"); 40 | const SqlAdapter = require("moleculer-db-adapter-sequelize"); 41 | const Sequelize = require("sequelize"); 42 | 43 | const broker = new ServiceBroker(); 44 | 45 | // Create a Sequelize service for `post` entities 46 | broker.createService({ 47 | name: "posts", 48 | mixins: [DbService], 49 | adapter: new SqlAdapter("sqlite://:memory:"), 50 | model: { 51 | name: "post", 52 | define: { 53 | title: Sequelize.STRING, 54 | content: Sequelize.TEXT, 55 | votes: Sequelize.INTEGER, 56 | author: Sequelize.INTEGER, 57 | status: Sequelize.BOOLEAN 58 | }, 59 | options: { 60 | // Options from http://docs.sequelizejs.com/manual/tutorial/models-definition.html 61 | } 62 | }, 63 | }); 64 | 65 | 66 | broker.start() 67 | // Create a new post 68 | .then(() => broker.call("posts.create", { 69 | title: "My first post", 70 | content: "Lorem ipsum...", 71 | votes: 0 72 | })) 73 | 74 | // Get all posts 75 | .then(() => broker.call("posts.find").then(console.log)); 76 | ``` 77 | 78 | ### Raw queries 79 | You can reach the `sequelize` instance via `this.adapter.db`. To call [Raw queries](http://docs.sequelizejs.com/manual/raw-queries.html): 80 | 81 | ```js 82 | actions: { 83 | findHello2() { 84 | return this.adapter.db.query("SELECT * FROM posts WHERE title = 'Hello 2' LIMIT 1") 85 | .then(([res, metadata]) => res); 86 | } 87 | } 88 | ``` 89 | 90 | ## Options 91 | Every constructor arguments are passed to the `Sequelize` constructor. Read more about [Sequelize connection](http://docs.sequelizejs.com/manual/installation/getting-started.html). 92 | 93 | **Example with connection URI** 94 | ```js 95 | new SqlAdapter("postgres://user:pass@example.com:5432/dbname"); 96 | ``` 97 | 98 | **Example with connection options** 99 | ```js 100 | new SqlAdapter('database', 'username', 'password', { 101 | host: 'localhost', 102 | dialect: /* one of 'mysql' | 'mariadb' | 'postgres' | 'mssql' */, 103 | 104 | pool: { 105 | max: 5, 106 | min: 0, 107 | idle: 10000 108 | }, 109 | 110 | // SQLite only 111 | storage: 'path/to/database.sqlite' 112 | }); 113 | ``` 114 | 115 | # Test 116 | ``` 117 | $ npm test 118 | ``` 119 | 120 | In development with watching 121 | 122 | ``` 123 | $ npm run ci 124 | ``` 125 | 126 | # License 127 | The project is available under the [MIT license](https://tldrlegal.com/license/mit-license). 128 | 129 | # Contact 130 | Copyright (c) 2016-2024 MoleculerJS 131 | 132 | [![@moleculerjs](https://img.shields.io/badge/github-moleculerjs-green.svg)](https://github.com/moleculerjs) [![@MoleculerJS](https://img.shields.io/badge/twitter-MoleculerJS-blue.svg)](https://twitter.com/MoleculerJS) 133 | -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-sequelize/examples/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const moduleName = process.argv[2] || "simple"; 4 | process.argv.splice(2, 1); 5 | 6 | require("./" + moduleName); -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-sequelize/examples/integration/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { ServiceBroker } = require("moleculer"); 4 | const StoreService = require("../../../moleculer-db/index"); 5 | const SequelizeAdapter = require("../../index"); 6 | const ModuleChecker = require("../../../moleculer-db/test/checker"); 7 | const Sequelize = require("sequelize"); 8 | 9 | const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); 10 | 11 | // Create broker 12 | const broker = new ServiceBroker({ 13 | logger: console, 14 | logLevel: "debug" 15 | }); 16 | 17 | /* 18 | Test environments: 19 | 20 | MySQL: 21 | Server: 22 | docker run --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=mysql -d mysql 23 | 24 | CLI: 25 | docker run -it --link mysql:mysql --rm mysql sh -c 'exec mysql -h"$MYSQL_PORT_3306_TCP_ADDR" -P"$MYSQL_PORT_3306_TCP_PORT" -uroot -p"$MYSQL_ENV_MYSQL_ROOT_PASSWORD"' 26 | 27 | CREATE DATABASE moleculer_test; 28 | SHOW DATABASES; 29 | 30 | PostgreSQL: 31 | Server: 32 | docker run --name postgres -p 5432:5432 -e POSTGRES_PASSWORD=postgres -d postgres 33 | 34 | CLI: 35 | docker run -it --rm --link postgres:postgres postgres psql -h postgres -U postgres 36 | 37 | CREATE DATABASE moleculer_test; 38 | \list 39 | 40 | */ 41 | 42 | // Load my service 43 | broker.createService(StoreService, { 44 | name: "posts", 45 | adapter: new SequelizeAdapter("sqlite://:memory:"), 46 | //adapter: new SequelizeAdapter({ dialect: "sqlite", storage: "d:\\moleculer-test.db"}), 47 | //adapter: new SequelizeAdapter("mssql://sa:@localhost/moleculer-test"), 48 | //adapter: new SequelizeAdapter("mysql://root:mysql@192.168.51.29/moleculer_test"), 49 | //adapter: new SequelizeAdapter("postgres://postgres:postgres@192.168.51.29/moleculer_test"), 50 | model: { 51 | name: "post", 52 | define: { 53 | title: Sequelize.STRING, 54 | content: Sequelize.TEXT, 55 | votes: Sequelize.INTEGER, 56 | author: Sequelize.INTEGER, 57 | status: Sequelize.BOOLEAN 58 | }, 59 | options: { 60 | 61 | } 62 | }, 63 | settings: { 64 | fields: ["id", "title", "content", "votes", "status", "updatedAt"] 65 | }, 66 | 67 | actions: { 68 | vote(ctx) { 69 | return this.adapter.findById(ctx.params.id) 70 | .then(post => post.increment({ votes: 1 })) 71 | .then(() => this.adapter.findById(ctx.params.id)) 72 | .then(doc => this.transformDocuments(ctx, ctx.params, doc)); 73 | }, 74 | 75 | unvote(ctx) { 76 | return this.adapter.findById(ctx.params.id) 77 | .then(post => post.decrement({ votes: 1 })) 78 | .then(() => this.adapter.findById(ctx.params.id)) 79 | .then(doc => this.transformDocuments(ctx, ctx.params, doc)); 80 | }, 81 | 82 | findRaw() { 83 | return this.adapter.db.query("SELECT * FROM posts WHERE title = 'Hello 2' LIMIT 1").then(([res]) => res); 84 | } 85 | }, 86 | 87 | afterConnected() { 88 | this.logger.info("Connected successfully"); 89 | return this.adapter.clear(); 90 | } 91 | }); 92 | 93 | const checker = new ModuleChecker(12); 94 | 95 | // Start checks 96 | function start() { 97 | broker.start() 98 | .then(() => delay(500)) 99 | .then(() => checker.execute()) 100 | .catch(console.error) 101 | .then(() => broker.stop()) 102 | .then(() => checker.printTotal()); 103 | } 104 | 105 | // --- TEST CASES --- 106 | 107 | let id =[]; 108 | 109 | // Count of posts 110 | checker.add("COUNT", () => broker.call("posts.count"), res => { 111 | console.log(res); 112 | return res == 0; 113 | }); 114 | 115 | // Create new Posts 116 | checker.add("--- CREATE ---", () => broker.call("posts.create", { title: "Hello", content: "Post content", votes: 2, status: true }), doc => { 117 | id = doc.id; 118 | console.log("Saved: ", doc); 119 | return doc.id && doc.title === "Hello" && doc.content === "Post content" && doc.votes === 2 && doc.status === true; 120 | }); 121 | 122 | // List posts 123 | checker.add("--- FIND ---", () => broker.call("posts.find"), res => { 124 | console.log(res); 125 | return res.length == 1 && res[0].id == id; 126 | }); 127 | 128 | // Get a post 129 | checker.add("--- GET ---", () => broker.call("posts.get", { id }), res => { 130 | console.log(res); 131 | return res.id == id; 132 | }); 133 | 134 | // Vote a post 135 | checker.add("--- VOTE ---", () => broker.call("posts.vote", { 136 | id 137 | }), res => { 138 | console.log(res); 139 | return res.id == id && res.votes === 3; 140 | }); 141 | 142 | // Update a posts 143 | checker.add("--- UPDATE ---", () => broker.call("posts.update", { 144 | id, 145 | title: "Hello 2", 146 | content: "Post content 2", 147 | updatedAt: new Date() 148 | }), doc => { 149 | console.log(doc); 150 | return doc.id && doc.title === "Hello 2" && doc.content === "Post content 2" && doc.votes === 3 && doc.status === true && doc.updatedAt; 151 | }); 152 | 153 | // Find a post by RAW query 154 | checker.add("--- FIND RAW ---", () => broker.call("posts.findRaw"), res => { 155 | console.log(res); 156 | return res.length == 1 && res[0].id == id; 157 | }); 158 | 159 | // Get a post 160 | checker.add("--- GET ---", () => broker.call("posts.get", { id }), doc => { 161 | console.log(doc); 162 | return doc.id == id && doc.title == "Hello 2" && doc.votes === 3; 163 | }); 164 | 165 | // Unvote a post 166 | checker.add("--- UNVOTE ---", () => broker.call("posts.unvote", { 167 | id 168 | }), res => { 169 | console.log(res); 170 | return res.id == id && res.votes === 2; 171 | }); 172 | 173 | // Count of posts 174 | checker.add("--- COUNT ---", () => broker.call("posts.count"), res => { 175 | console.log(res); 176 | return res == 1; 177 | }); 178 | 179 | // Remove a post 180 | checker.add("--- REMOVE ---", () => broker.call("posts.remove", { id }), res => { 181 | console.log(res); 182 | return res.id == id; 183 | }); 184 | 185 | // Count of posts 186 | checker.add("--- COUNT ---", () => broker.call("posts.count"), res => { 187 | console.log(res); 188 | return res == 0; 189 | }); 190 | 191 | 192 | start(); 193 | -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-sequelize/examples/simple/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { ServiceBroker } = require("moleculer"); 4 | const StoreService = require("../../../moleculer-db/index"); 5 | const ModuleChecker = require("../../../moleculer-db/test/checker"); 6 | const SequelizeAdapter = require("../../index"); 7 | const Sequelize = require("sequelize"); 8 | const Op = Sequelize.Op; 9 | 10 | const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); 11 | 12 | process.on("warning", e => console.warn(e.stack)); 13 | 14 | // Create broker 15 | const broker = new ServiceBroker({ 16 | logger: console, 17 | logLevel: "debug" 18 | }); 19 | let adapter; 20 | 21 | // Load my service 22 | broker.createService(StoreService, { 23 | name: "posts", 24 | adapter: new SequelizeAdapter("sqlite://:memory:"), 25 | model: { 26 | name: "post", 27 | define: { 28 | title: Sequelize.STRING, 29 | content: Sequelize.TEXT, 30 | votes: Sequelize.INTEGER, 31 | author: Sequelize.INTEGER, 32 | status: Sequelize.BOOLEAN 33 | }, 34 | options: { 35 | 36 | } 37 | }, 38 | settings: {}, 39 | 40 | afterConnected() { 41 | adapter = this.adapter; 42 | return this.adapter.clear(); 43 | } 44 | }); 45 | 46 | const checker = new ModuleChecker(22); 47 | 48 | // Start checks 49 | function start() { 50 | broker.start() 51 | .then(() => delay(500)) 52 | .then(() => checker.execute()) 53 | .catch(console.error) 54 | .then(() => broker.stop()) 55 | .then(() => checker.printTotal()); 56 | } 57 | 58 | // --- TEST CASES --- 59 | 60 | let ids =[]; 61 | let date = new Date(); 62 | 63 | // Count of posts 64 | checker.add("COUNT", () => adapter.count(), res => { 65 | console.log(res); 66 | return res == 0; 67 | }); 68 | 69 | // Insert a new Post 70 | checker.add("INSERT", () => adapter.insert({ title: "Hello", content: "Post content", votes: 3, status: true, createdAt: date }), doc => { 71 | ids[0] = doc.id; 72 | console.log("Saved: ", adapter.entityToObject(doc)); 73 | return doc.id && doc.title === "Hello" && doc.content === "Post content" && doc.votes === 3 && doc.status === true && doc.createdAt === date; 74 | }); 75 | 76 | // Find 77 | checker.add("FIND", () => adapter.find({}), res => { 78 | console.log(res.map(adapter.entityToObject)); 79 | return res.length == 1 && res[0].id == ids[0]; 80 | }); 81 | 82 | // Find by ID 83 | checker.add("GET", () => adapter.findById(ids[0]), res => { 84 | console.log(adapter.entityToObject(res)); 85 | return res.id == ids[0]; 86 | }); 87 | 88 | // Count of posts 89 | checker.add("COUNT", () => adapter.count(), res => { 90 | console.log(res); 91 | return res == 1; 92 | }); 93 | 94 | // Insert many new Posts 95 | checker.add("INSERT MANY", () => adapter.insertMany([ 96 | { title: "Second", content: "Second post content", votes: 8, status: true, createdAt: new Date() }, 97 | { title: "Last", content: "Last document", votes: 1, status: false, createdAt: new Date() } 98 | ]), docs => { 99 | console.log("Saved: ", docs.map(adapter.entityToObject)); 100 | ids[1] = docs[0].id; 101 | ids[2] = docs[1].id; 102 | 103 | return [ 104 | docs.length == 2, 105 | ids[1] && docs[0].title === "Second" && docs[0].votes === 8, 106 | ids[1] && docs[1].title === "Last" && docs[1].votes === 1 && docs[1].status === false 107 | ]; 108 | }); 109 | 110 | // Count of posts 111 | checker.add("COUNT", () => adapter.count(), res => { 112 | console.log(res); 113 | return res == 3; 114 | }); 115 | 116 | // Find 117 | checker.add("FIND by query", () => adapter.find({ query: { title: "Last" } }), res => { 118 | console.log(res.map(adapter.entityToObject)); 119 | return res.length == 1 && res[0].id == ids[2]; 120 | }); 121 | 122 | // Find 123 | checker.add("FIND by limit, sort, query", () => adapter.find({ limit: 1, sort: ["votes", "-title"], offset: 1 }), res => { 124 | console.log(res.map(adapter.entityToObject)); 125 | return res.length == 1 && res[0].id == ids[0]; 126 | }); 127 | 128 | // Find 129 | checker.add("FIND by query (Op.gt)", () => adapter.find({ query: { votes: { [Op.gt]: 2 } } }), res => { 130 | console.log(res.map(adapter.entityToObject)); 131 | return res.length == 2; 132 | }); 133 | 134 | // Find 135 | checker.add("COUNT by query (Op.gt)", () => adapter.count({ query: { votes: { [Op.gt]: 2 } } }), res => { 136 | console.log(res); 137 | return res == 2; 138 | }); 139 | 140 | // Find 141 | checker.add("FIND by text search", () => adapter.find({ search: "content", searchFields: ["title", "content"] }), res => { 142 | console.log(res.map(adapter.entityToObject)); 143 | return [ 144 | res.length == 2 145 | ]; 146 | }); 147 | 148 | // Find by IDs 149 | checker.add("GET BY IDS", () => adapter.findByIds([ids[2], ids[0]]), res => { 150 | console.log(res.map(adapter.entityToObject)); 151 | return res.length == 2; 152 | }); 153 | 154 | // Update a posts 155 | checker.add("UPDATE", () => adapter.updateById(ids[2], { $set: { 156 | title: "Last 2", 157 | updatedAt: new Date(), 158 | status: true 159 | }}), doc => { 160 | console.log("Updated: ", adapter.entityToObject(doc)); 161 | return doc.id && doc.title === "Last 2" && doc.content === "Last document" && doc.votes === 1 && doc.status === true && doc.updatedAt; 162 | }); 163 | 164 | // Update by query 165 | checker.add("UPDATE BY QUERY", () => adapter.updateMany({ votes: { [Op.lt]: 5 }}, { 166 | $set: { status: false } 167 | }), count => { 168 | console.log("Updated: ", count); 169 | return count == 2; 170 | }); 171 | 172 | // Remove by query 173 | checker.add("REMOVE BY QUERY", () => adapter.removeMany({ votes: { [Op.lt]: 5 }}), count => { 174 | console.log("Removed: ", count); 175 | return count == 2; 176 | }); 177 | 178 | // Count of posts 179 | checker.add("COUNT", () => adapter.count(), res => { 180 | console.log(res); 181 | return res == 1; 182 | }); 183 | 184 | // Remove by ID 185 | checker.add("REMOVE BY ID", () => adapter.removeById(ids[1]), doc => { 186 | console.log("Removed: ", adapter.entityToObject(doc)); 187 | return doc && doc.id == ids[1]; 188 | }); 189 | 190 | // Count of posts 191 | checker.add("COUNT", () => adapter.count(), res => { 192 | console.log(res); 193 | return res == 0; 194 | }); 195 | 196 | // Clear 197 | checker.add("CLEAR", () => adapter.clear(), res => { 198 | console.log(res); 199 | return res == 0; 200 | }); 201 | 202 | start(); 203 | -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-sequelize/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module "moleculer-db-adapter-sequelize" { 2 | import { Service, ServiceBroker } from "moleculer"; 3 | import { Sequelize } from "sequelize/types"; 4 | 5 | 6 | export interface QueryOptions { 7 | [name: string]: any; 8 | } 9 | export interface CursorOptions extends FilterOptions { 10 | sort?: string | string[]; 11 | fields?: string | string[]; 12 | } 13 | 14 | export interface FilterOptions { 15 | limit?: string | number; 16 | offset?: string | number; 17 | searchFields?: string | string[]; 18 | search?: string; 19 | query?: QueryOptions; 20 | } 21 | 22 | export interface CountOptions { 23 | searchFields?: string | string[]; 24 | search?: string; 25 | 26 | } 27 | 28 | export default class SequelizeDbAdapter { 29 | 30 | constructor(opts: object | string); 31 | /** 32 | * Initialize adapter 33 | * 34 | * @param {ServiceBroker} broker 35 | * @param {Service} service 36 | * @memberof DbAdapter 37 | */ 38 | init(broker: ServiceBroker, service: Service): void; 39 | /** 40 | * Connect to database 41 | * 42 | * @returns {Promise} 43 | * @memberof DbAdapter 44 | */ 45 | connect(): Promise; 46 | /** 47 | * Disconnect from database 48 | * 49 | * @returns {Promise} 50 | * @memberof DbAdapter 51 | */ 52 | disconnect(): Promise; 53 | 54 | /** 55 | * Find all entities by filters. 56 | * 57 | * Available filter props: 58 | * - limit 59 | * - offset 60 | * - sort 61 | * - search 62 | * - searchFields 63 | * - query 64 | * 65 | * @param {Object} filters 66 | * @returns {Promise} 67 | * @memberof DbAdapter 68 | */ 69 | find(filters: FilterOptions): Promise; 70 | 71 | /** 72 | * Find an entity by query 73 | * 74 | * @param {Object} query 75 | * @returns {Promise} 76 | * @memberof DbAdapter 77 | */ 78 | findOne(query: Q): Promise; 79 | 80 | /** 81 | * Find an entity by ID 82 | * 83 | * @param {any} id 84 | * @returns {Promise} 85 | * @memberof DbAdapter 86 | */ 87 | findById(id: any): Promise; 88 | 89 | /** 90 | * Find all entites by IDs 91 | * 92 | * @param {Array} ids 93 | * @returns {Promise} 94 | * @memberof DbAdapter 95 | */ 96 | findByIds(ids: (string | number)[]): Promise; 97 | 98 | /** 99 | * Get count of filtered entites 100 | * 101 | * Available filter props: 102 | * - search 103 | * - searchFields 104 | * - query 105 | * 106 | * @param {Object} [filters={}] 107 | * @returns {Promise} 108 | * @memberof DbAdapter 109 | */ 110 | count(filters?: CountOptions): Promise; 111 | 112 | /** 113 | * Insert an entity 114 | * 115 | * @param {Object} entity 116 | * @returns {Promise} 117 | * @memberof MemoryDbAdapter 118 | */ 119 | insert(entity: object): Promise; 120 | 121 | /** 122 | * Insert multiple entities 123 | * 124 | * @param {Array} entities 125 | * @returns {Promise} 126 | * @memberof MemoryDbAdapter 127 | */ 128 | insertMany(...entities: object[]): Promise; 129 | 130 | /** 131 | * Update many entities by `query` and `update` 132 | * 133 | * @param {Object} query 134 | * @param {Object} update 135 | * @returns {Promise} 136 | * @memberof DbAdapter 137 | */ 138 | updateMany( 139 | query: Q, 140 | update: object 141 | ): Promise; 142 | 143 | /** 144 | * Update an entity by ID 145 | * 146 | * @param {string|number} id 147 | * @param {Object} update 148 | * @returns {Promise} 149 | * @memberof DbAdapter 150 | */ 151 | updateById(id: string | number, update: object): Promise; 152 | 153 | /** 154 | * Remove many entities which are matched by `query` 155 | * 156 | * @param {Object} query 157 | * @returns {Promise} 158 | * @memberof DbAdapter 159 | */ 160 | removeMany(query: QueryOptions): Promise; 161 | 162 | /** 163 | * Remove an entity by ID 164 | * 165 | * @param {number|string} id 166 | * @returns {Promise} 167 | * @memberof DbAdapter 168 | */ 169 | removeById(id: number | string): Promise; 170 | 171 | /** 172 | * Clear all entities from DB 173 | * 174 | * @returns {Promise} 175 | * @memberof DbAdapter 176 | */ 177 | clear(): Promise; 178 | 179 | /** 180 | * Convert DB entity to JSON object 181 | * 182 | * @param {any} entity 183 | * @returns {Object} 184 | * @memberof DbAdapter 185 | */ 186 | entityToObject(entity: any): object; 187 | 188 | /** 189 | * Add filters to query 190 | * 191 | * Available filters: 192 | * - search 193 | * - searchFields 194 | * - sort 195 | * - limit 196 | * - offset 197 | * - query 198 | * 199 | * @param {Object} params 200 | * @returns {any} 201 | * @memberof DbAdapter 202 | */ 203 | createCursor(params: CursorOptions): any; 204 | 205 | /** 206 | * Transforms 'idField' into NeDB's '_id' 207 | * @param {Object} entity 208 | * @param {String} idField 209 | * @memberof DbAdapter 210 | * @returns {Object} Modified entity 211 | */ 212 | beforeSaveTransformID(entity: object, idField: string): object; 213 | 214 | /** 215 | * Transforms NeDB's '_id' into user defined 'idField' 216 | * @param {Object} entity 217 | * @param {String} idField 218 | * @memberof DbAdapter 219 | * @returns {Object} Modified entity 220 | */ 221 | afterRetrieveTransformID(entity: object, idField: string): object; 222 | findByIds(ids: (string | number)[]): Promise; 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-sequelize/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer-db-adapter-sequelize 3 | * Copyright (c) 2019 MoleculerJS (https://github.com/moleculerjs/moleculer-db) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | module.exports = require("./src"); 10 | -------------------------------------------------------------------------------- /packages/moleculer-db-adapter-sequelize/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "moleculer-db-adapter-sequelize", 3 | "version": "0.2.19", 4 | "description": "SQL adapter (Postgres, MySQL, SQLite & MSSQL) for Moleculer DB service", 5 | "main": "index.js", 6 | "types": "./index.d.ts", 7 | "scripts": { 8 | "dev": "nodemon examples/index.js", 9 | "ci": "jest --watch", 10 | "test": "jest --coverage", 11 | "lint": "eslint --ext=.js src test", 12 | "deps": "npm-check -u", 13 | "readme": "node node_modules/moleculer-docgen/index.js -t README.md src/index.js", 14 | "coverall": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js" 15 | }, 16 | "keywords": [ 17 | "microservice", 18 | "moleculer", 19 | "orm", 20 | "database" 21 | ], 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/moleculerjs/moleculer-db.git" 25 | }, 26 | "homepage": "https://github.com/moleculerjs/moleculer-db/tree/master/packages/moleculer-db-adapter-sequelize#readme", 27 | "author": "MoleculerJS", 28 | "license": "MIT", 29 | "peerDependencies": { 30 | "moleculer": "^0.12.0 || ^0.13.0 || ^0.14.0 || ^0.15.0", 31 | "sequelize": "^5.9.4 || ^6.7.0" 32 | }, 33 | "devDependencies": { 34 | "@types/jest": "^27.0.2", 35 | "benchmarkify": "^3.0.0", 36 | "coveralls": "^3.1.1", 37 | "eslint": "^8.21.0", 38 | "jest": "^27.2.5", 39 | "jest-cli": "^27.2.5", 40 | "lolex": "^6.0.0", 41 | "moleculer": "^0.14.22", 42 | "moleculer-docgen": "^0.3.0", 43 | "mysql2": "^2.3.3", 44 | "nodemon": "^2.0.19", 45 | "npm-check": "^6.0.1", 46 | "pg": "^8.7.3", 47 | "pg-hstore": "^2.3.4", 48 | "sequelize": "^6.35.0", 49 | "sqlite3": "^5.1.7", 50 | "tedious": "^11.0.8" 51 | }, 52 | "jest": { 53 | "testEnvironment": "node", 54 | "coveragePathIgnorePatterns": [ 55 | "/node_modules/", 56 | "/test/services/" 57 | ] 58 | }, 59 | "engines": { 60 | "node": ">= 8.x.x" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /packages/moleculer-db/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | coverage/ 4 | npm-debug.log 5 | stats.json 6 | yarn-error.log -------------------------------------------------------------------------------- /packages/moleculer-db/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 MoleculerJS 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 | -------------------------------------------------------------------------------- /packages/moleculer-db/examples/encode/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { ServiceBroker } = require("moleculer"); 4 | const DbService = require("../../index"); 5 | const _ = require("lodash"); 6 | const ModuleChecker = require("../../test/checker"); 7 | 8 | const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); 9 | 10 | // Create broker 11 | const broker = new ServiceBroker({ 12 | logger: console, 13 | logLevel: "debug" 14 | }); 15 | 16 | // Load my service 17 | 18 | broker.createService(DbService, { 19 | name: "products", 20 | adapter: new DbService.MemoryAdapter(), 21 | settings: { 22 | fields: ["_id", "name"] 23 | }, 24 | 25 | methods: { 26 | 27 | encodeID(id) { 28 | return "prod-" + id; 29 | }, 30 | 31 | decodeID(id) { 32 | if (id.startsWith("prod-")) 33 | return id.slice(5); 34 | } 35 | 36 | }, 37 | 38 | afterConnected() { 39 | this.logger.info("Connected successfully"); 40 | return this.adapter.count().delay(500).then(count => { 41 | if (count == 0) { 42 | this.logger.info("Seed products..."); 43 | const products = _.times(20, i => { 44 | return { 45 | name: "Product " + i 46 | }; 47 | }); 48 | return this.adapter.insertMany(products) 49 | .then(() => this.adapter.count()) 50 | .then(count => console.log("Saved products:", count )); 51 | 52 | } 53 | }); 54 | } 55 | }); 56 | 57 | const checker = new ModuleChecker(6); 58 | 59 | // Start checks 60 | function start() { 61 | return Promise.resolve() 62 | .then(() => delay(500)) 63 | .then(() => checker.execute()) 64 | .catch(console.error) 65 | .then(() => broker.stop()) 66 | .then(() => checker.printTotal()); 67 | } 68 | 69 | // --- TEST CASES --- 70 | 71 | let id; 72 | 73 | // List posts 74 | checker.add("FIND PRODUCTS", () => broker.call("products.find", { limit: 5 }), rows => { 75 | console.log(rows); 76 | id = rows[3]._id; 77 | 78 | return rows.length === 5 && id.startsWith("prod-"); 79 | }); 80 | 81 | // Get by encoded ID 82 | checker.add("GET BY ID", () => broker.call("products.get", { id }), res => { 83 | console.log(res); 84 | return res && res._id === id; 85 | }); 86 | 87 | // Update a product 88 | checker.add("UPDATE", () => broker.call("products.update", { 89 | id, 90 | name: "Modified product" 91 | }), res => { 92 | console.log(res); 93 | return res && res.name === "Modified product"; 94 | }); 95 | 96 | // Get by encoded ID 97 | checker.add("GET BY ID w/ mapping", () => broker.call("products.get", { id: [id], mapping: true }), res => { 98 | console.log(res); 99 | return res && res[id] && res[id]._id === id; 100 | }); 101 | 102 | // Remove by ID 103 | checker.add("REMOVE BY ID", () => broker.call("products.remove", { id }), res => { 104 | console.log(res); 105 | return res === 1; 106 | }); 107 | 108 | // Count of products 109 | checker.add("COUNT", () => broker.call("products.count"), res => { 110 | console.log(res); 111 | return res === 19; 112 | }); 113 | 114 | 115 | broker.start().then(() => start()); 116 | -------------------------------------------------------------------------------- /packages/moleculer-db/examples/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const moduleName = process.argv[2] || "simple"; 4 | process.argv.splice(2, 1); 5 | 6 | require("./" + moduleName); -------------------------------------------------------------------------------- /packages/moleculer-db/examples/pagination/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const _ = require("lodash"); 4 | const kleur = require("kleur"); 5 | const { ServiceBroker } = require("moleculer"); 6 | const DbService = require("../../index"); 7 | const ModuleChecker = require("../../test/checker"); 8 | 9 | const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); 10 | 11 | // Create broker 12 | const broker = new ServiceBroker({ 13 | logger: console, 14 | logLevel: "debug" 15 | }); 16 | 17 | // Load my service 18 | broker.createService(DbService, { 19 | name: "posts", 20 | settings: { 21 | fields: ["title"] 22 | }, 23 | 24 | methods: { 25 | seedDB() { 26 | return this.adapter.clear() 27 | .then(() => this.adapter.insertMany(_.times(28, i => { 28 | return { 29 | title: `Post #${_.padStart(i + 1, 2, "0")}` 30 | }; 31 | }))); 32 | } 33 | }, 34 | 35 | afterConnected() { 36 | this.logger.info(kleur.green().bold("Connected successfully")); 37 | return this.seedDB(); 38 | } 39 | }); 40 | 41 | const checker = new ModuleChecker(14); 42 | 43 | // Start checks 44 | function start() { 45 | return Promise.resolve() 46 | .then(()=> delay(500)) 47 | .then(() => checker.execute()) 48 | .catch(console.error) 49 | .then(() => broker.stop()) 50 | .then(() => checker.printTotal()); 51 | } 52 | 53 | // --- TEST CASES --- 54 | 55 | // Count of posts 56 | checker.add("COUNT", () => broker.call("posts.count"), res => { 57 | console.log(res); 58 | return res == 28; 59 | }); 60 | 61 | // Find posts 62 | checker.add("FIND", () => broker.call("posts.find", { sort: "title" }), res => { 63 | console.log(res); 64 | return res.length == 28; 65 | }); 66 | 67 | // List posts 68 | checker.add("LIST FIRST 10", () => broker.call("posts.list", { sort: "title" }), res => { 69 | console.log(res); 70 | const rows = res.rows; 71 | return [ 72 | res.total === 28 && res.page === 1 && res.pageSize === 10 && res.totalPages === 3, 73 | rows.length === 10 && rows[0].title == "Post #01" && rows[9].title === "Post #10" 74 | ]; 75 | }); 76 | 77 | // List posts 78 | checker.add("LIST LAST 10", () => broker.call("posts.list", { sort: "-title" }), res => { 79 | console.log(res); 80 | const rows = res.rows; 81 | return [ 82 | res.total === 28 && res.page === 1 && res.pageSize === 10 && res.totalPages === 3, 83 | rows.length === 10 && rows[0].title == "Post #28" && rows[9].title === "Post #19" 84 | ]; 85 | }); 86 | 87 | // List posts 88 | checker.add("LIST FIRST 25", () => broker.call("posts.list", { page: 1, pageSize: 25, sort: "title" }), res => { 89 | console.log(res); 90 | const rows = res.rows; 91 | return [ 92 | res.total === 28 && res.page === 1 && res.pageSize === 25 && res.totalPages === 2, 93 | rows.length === 25 && rows[0].title == "Post #01" && rows[24].title === "Post #25" 94 | ]; 95 | }); 96 | 97 | // List posts 98 | checker.add("LIST NEXT 25", () => broker.call("posts.list", { page: 2, pageSize: 25, sort: "title" }), res => { 99 | console.log(res); 100 | const rows = res.rows; 101 | return [ 102 | res.total === 28 && res.page === 2 && res.pageSize === 25 && res.totalPages === 2, 103 | rows.length === 3 && rows[0].title == "Post #26" && rows[2].title === "Post #28" 104 | ]; 105 | }); 106 | 107 | // List posts 108 | checker.add("LIST NEXT2 25", () => broker.call("posts.list", { page: 3, pageSize: 25, sort: "title" }), res => { 109 | console.log(res); 110 | const rows = res.rows; 111 | return [ 112 | res.total === 28 && res.page === 3 && res.pageSize === 25 && res.totalPages === 2, 113 | rows.length === 0 114 | ]; 115 | }); 116 | 117 | // List posts with search 118 | checker.add("LIST SEARCH 5", () => broker.call("posts.list", { page: 1, pageSize: 5, search: "#2" }), res => { 119 | console.log(res); 120 | const rows = res.rows; 121 | return [ 122 | res.total === 9 && res.page === 1 && res.pageSize === 5 && res.totalPages === 2, 123 | rows.length === 5 124 | ]; 125 | }); 126 | 127 | broker.start().then(() => start()); 128 | -------------------------------------------------------------------------------- /packages/moleculer-db/examples/populates/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { ServiceBroker } = require("moleculer"); 4 | const DbService = require("../../index"); 5 | const path = require("node:path"); 6 | const ModuleChecker = require("../../test/checker"); 7 | 8 | const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); 9 | 10 | // Create broker 11 | const broker = new ServiceBroker({ 12 | logger: console, 13 | logLevel: "debug" 14 | }); 15 | 16 | let posts = []; 17 | let users = []; 18 | 19 | // Load my service 20 | 21 | broker.createService(DbService, { 22 | name: "posts", 23 | adapter: new DbService.MemoryAdapter({ filename: path.join(__dirname, "posts.db") }), 24 | settings: { 25 | fields: ["_id", "title", "content", "votes", "author"], 26 | 27 | populates: { 28 | "author": { 29 | action: "users.get", 30 | params: { 31 | fields: ["username", "fullName"] 32 | } 33 | } 34 | } 35 | }, 36 | 37 | methods: { 38 | 39 | encodeID(id) { 40 | return "post-" + id; 41 | }, 42 | 43 | decodeID(id) { 44 | if (id.startsWith("post-")) 45 | return id.slice(5); 46 | } 47 | 48 | }, 49 | 50 | afterConnected() { 51 | this.logger.info("Connected successfully"); 52 | return this.adapter.clear().delay(1000).then(() => { 53 | if (users.length == 0) return; 54 | 55 | this.logger.info("Seed Posts collection..."); 56 | return this.adapter.insertMany([ 57 | { title: "1st post", content: "First post content.", votes: 3, author: users[2]._id }, 58 | { title: "2nd post", content: "Labore eum veritatis ut.", votes: 8, author: users[1]._id }, 59 | { title: "3rd post", content: "Rerum deleniti repellendus error ea.", votes: 0, author: users[4]._id }, 60 | { title: "4th post", content: "Normal post content.", votes: 4, author: users[3]._id }, 61 | { title: "5th post", content: "Voluptatum praesentium voluptatibus est nesciunt fugiat.", votes: 6, author: users[1]._id } 62 | ]).then(docs => this.transformDocuments(null, {}, docs)) 63 | .then(docs => posts = docs); 64 | }); 65 | } 66 | }); 67 | 68 | // Load my service 69 | broker.createService(DbService, { 70 | name: "users", 71 | adapter: new DbService.MemoryAdapter({ filename: path.join(__dirname, "users.db") }), 72 | settings: { 73 | fields: ["_id", "username", "fullName", "email"] 74 | }, 75 | 76 | methods: { 77 | 78 | encodeID(id) { 79 | return "user-" + id; 80 | }, 81 | 82 | decodeID(id) { 83 | if (id.startsWith("user-")) 84 | return id.slice(5); 85 | } 86 | 87 | }, 88 | 89 | afterConnected() { 90 | this.logger.info("Connected successfully"); 91 | return this.adapter.clear().then(() => { 92 | this.logger.info("Seed Users collection..."); 93 | return this.adapter.insertMany([ 94 | { username: "John", fullName: "John Doe", email: "john.doe@gmail.com", status: 1 }, 95 | { username: "Adam", fullName: "Adam Doe", email: "adam.doe@gmail.com", status: 1 }, 96 | { username: "Jane", fullName: "Jane Doe", email: "jane.doe@gmail.com", status: 0 }, 97 | { username: "Susan", fullName: "Susan Doe", email: "susan.doe@gmail.com", status: 1 }, 98 | { username: "Bill", fullName: "Bill Doe", email: "bill.doe@gmail.com", status: 1 } 99 | ]).then(docs => this.transformDocuments(null, {}, docs)) 100 | .then(docs => { 101 | users = docs; 102 | }); 103 | }); 104 | } 105 | }); 106 | 107 | const checker = new ModuleChecker(13); 108 | 109 | // Start checks 110 | function start() { 111 | return Promise.resolve() 112 | .then(()=> delay(500)) 113 | .then(() => checker.execute()) 114 | .catch(console.error) 115 | .then(() => broker.stop()) 116 | .then(() => checker.printTotal()); 117 | } 118 | 119 | // --- TEST CASES --- 120 | 121 | checker.add("FIND POSTS (search: 'content')", () => broker.call("posts.find", { limit: 0, offset: 0, sort: "-votes title", search: "content", populate: ["author"], fields: ["_id", "title", "author"] }), res => { 122 | console.log(res); 123 | return [ 124 | res.length == 2 && res[0]._id == posts[3]._id && res[1]._id == posts[0]._id, 125 | res[0].title && res[0].author && res[0].author.username == "Susan" && res[0].votes == null && res[0].author.email == null, 126 | res[1].title && res[1].author && res[1].author.username == "Jane" && res[1].votes == null && res[1].author.email == null 127 | ]; 128 | }); 129 | 130 | checker.add("COUNT POSTS (search: 'content')", () => broker.call("posts.count", { search: "content" }), res => { 131 | console.log(res); 132 | return res == 2; 133 | }); 134 | 135 | checker.add("FIND POSTS (limit: 3, offset: 2, sort: title, no author)", () => broker.call("posts.find", { limit: 3, offset: 2, sort: "title", fields: ["title", "votes"] }), res => { 136 | console.log(res); 137 | return [ 138 | res.length == 3, 139 | res[0].title && res[0].author == null && res[0].votes == 0, 140 | res[1].title && res[1].author == null && res[1].votes == 4, 141 | res[2].title && res[2].author == null && res[2].votes == 6 142 | ]; 143 | }); 144 | 145 | checker.add("GET POST (page: 2, pageSize: 5, sort: -votes)", () => broker.call("posts.get", { id: posts[2]._id, populate: ["author"], fields: ["title", "author"] }), res => { 146 | console.log(res); 147 | return res.title === "3rd post" && res.author.username === "Bill" && res.author.fullName === "Bill Doe" && res.author.email == null && res.votes == null; 148 | }); 149 | 150 | checker.add("LIST POSTS (page: 2, pageSize: 5, sort: -votes)", () => broker.call("posts.list", { page: 2, pageSize: 2, sort: "-votes", populate: ["author"], fields: ["_id", "title", "votes", "author"] }), res => { 151 | console.log(res); 152 | const rows = res.rows; 153 | return [ 154 | res.total === 5 && res.page === 2 && res.pageSize === 2 && res.totalPages === 3, 155 | rows.length == 2, 156 | rows[0]._id == posts[3]._id && rows[0].title && rows[0].author.username == "Susan" && rows[0].votes == 4, 157 | rows[1]._id == posts[0]._id && rows[1].title && rows[1].author.username == "Jane" && rows[1].votes == 3 158 | ]; 159 | }); 160 | 161 | broker.start().then(() => start()); 162 | -------------------------------------------------------------------------------- /packages/moleculer-db/examples/populates/posts.db: -------------------------------------------------------------------------------- 1 | {"title":"5th post","content":"Voluptatum praesentium voluptatibus est nesciunt fugiat.","votes":6,"author":"user-NCswTaXDSTUngn2N","_id":"8c3vCbkFyd79cKxZ"} 2 | {"title":"4th post","content":"Normal post content.","votes":4,"author":"user-cXhfflrqBJ4ZymTq","_id":"NhXNos6R9AddlweC"} 3 | {"title":"3rd post","content":"Rerum deleniti repellendus error ea.","votes":0,"author":"user-TUEwODS3pyDQUgAD","_id":"QCWpkjwh2OUWWlog"} 4 | {"title":"2nd post","content":"Labore eum veritatis ut.","votes":8,"author":"user-NCswTaXDSTUngn2N","_id":"uAJTuLVKPkE549YG"} 5 | {"title":"1st post","content":"First post content.","votes":3,"author":"user-UtiL2sKWnKdWstUt","_id":"wx6RyHnBiT4VoVg8"} 6 | {"$$deleted":true,"_id":"8c3vCbkFyd79cKxZ"} 7 | {"$$deleted":true,"_id":"NhXNos6R9AddlweC"} 8 | {"$$deleted":true,"_id":"QCWpkjwh2OUWWlog"} 9 | {"$$deleted":true,"_id":"uAJTuLVKPkE549YG"} 10 | {"$$deleted":true,"_id":"wx6RyHnBiT4VoVg8"} 11 | {"title":"1st post","content":"First post content.","votes":3,"author":"user-gjb5oURFi7BdIk4C","_id":"bwZahSBJVQVrbKWd"} 12 | {"title":"2nd post","content":"Labore eum veritatis ut.","votes":8,"author":"user-YclZ9Ndr1GR7x6bB","_id":"VpAdbY7kIIo6VyTh"} 13 | {"title":"3rd post","content":"Rerum deleniti repellendus error ea.","votes":0,"author":"user-9HEKYU8MiqwFcil5","_id":"Xx5pc6S2CDZeUhJ3"} 14 | {"title":"4th post","content":"Normal post content.","votes":4,"author":"user-TTrU3V8j8Z3Tn74D","_id":"5NDM95YCMGKSt2mk"} 15 | {"title":"5th post","content":"Voluptatum praesentium voluptatibus est nesciunt fugiat.","votes":6,"author":"user-YclZ9Ndr1GR7x6bB","_id":"Vywycpum8t7HS6g4"} 16 | -------------------------------------------------------------------------------- /packages/moleculer-db/examples/populates/users.db: -------------------------------------------------------------------------------- 1 | {"username":"John","fullName":"John Doe","email":"john.doe@gmail.com","status":1,"_id":"2hoopsBB2CKgmOar"} 2 | {"username":"Adam","fullName":"Adam Doe","email":"adam.doe@gmail.com","status":1,"_id":"NCswTaXDSTUngn2N"} 3 | {"username":"Bill","fullName":"Bill Doe","email":"bill.doe@gmail.com","status":1,"_id":"TUEwODS3pyDQUgAD"} 4 | {"username":"Jane","fullName":"Jane Doe","email":"jane.doe@gmail.com","status":0,"_id":"UtiL2sKWnKdWstUt"} 5 | {"username":"Susan","fullName":"Susan Doe","email":"susan.doe@gmail.com","status":1,"_id":"cXhfflrqBJ4ZymTq"} 6 | {"$$deleted":true,"_id":"2hoopsBB2CKgmOar"} 7 | {"$$deleted":true,"_id":"NCswTaXDSTUngn2N"} 8 | {"$$deleted":true,"_id":"TUEwODS3pyDQUgAD"} 9 | {"$$deleted":true,"_id":"UtiL2sKWnKdWstUt"} 10 | {"$$deleted":true,"_id":"cXhfflrqBJ4ZymTq"} 11 | {"username":"John","fullName":"John Doe","email":"john.doe@gmail.com","status":1,"_id":"VTuc5xBhV1T5wx2R"} 12 | {"username":"Adam","fullName":"Adam Doe","email":"adam.doe@gmail.com","status":1,"_id":"YclZ9Ndr1GR7x6bB"} 13 | {"username":"Jane","fullName":"Jane Doe","email":"jane.doe@gmail.com","status":0,"_id":"gjb5oURFi7BdIk4C"} 14 | {"username":"Susan","fullName":"Susan Doe","email":"susan.doe@gmail.com","status":1,"_id":"TTrU3V8j8Z3Tn74D"} 15 | {"username":"Bill","fullName":"Bill Doe","email":"bill.doe@gmail.com","status":1,"_id":"9HEKYU8MiqwFcil5"} 16 | -------------------------------------------------------------------------------- /packages/moleculer-db/examples/simple/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const kleur = require("kleur"); 4 | const { ServiceBroker } = require("moleculer"); 5 | const DbService = require("../../index"); 6 | const ModuleChecker = require("../../test/checker"); 7 | 8 | const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); 9 | 10 | // Create broker 11 | const broker = new ServiceBroker({ 12 | logger: console, 13 | logLevel: "debug" 14 | }); 15 | 16 | // Load my service 17 | broker.createService(DbService, { 18 | name: "posts", 19 | settings: { 20 | fields: ["_id", "title", "content", "votes", "createdAt", "updatedAt"] 21 | }, 22 | 23 | actions: { 24 | vote(ctx) { 25 | return this.adapter.updateById(ctx.params.id, { $inc: { votes: 1 } }); 26 | }, 27 | 28 | unvote(ctx) { 29 | return this.adapter.updateById(ctx.params.id, { $inc: { votes: -1 } }); 30 | } 31 | }, 32 | 33 | afterConnected() { 34 | this.logger.info(kleur.green().bold("Connected successfully")); 35 | this.adapter.clear(); 36 | }, 37 | 38 | entityCreated(json) { 39 | this.logger.info(kleur.cyan().bold("Entity lifecycle event: CREATED")/*, json*/); 40 | }, 41 | 42 | entityUpdated(json) { 43 | this.logger.info(kleur.cyan().bold("Entity lifecycle event: UPDATED")/*, json*/); 44 | }, 45 | 46 | entityRemoved(json) { 47 | this.logger.info(kleur.cyan().bold("Entity lifecycle event: REMOVED")/*, json*/); 48 | } 49 | }); 50 | 51 | 52 | const checker = new ModuleChecker(15); 53 | 54 | // Start checks 55 | function start() { 56 | return Promise.resolve() 57 | .then(()=> delay(500)) 58 | .then(() => checker.execute()) 59 | .catch(console.error) 60 | .then(() => broker.stop()) 61 | .then(() => checker.printTotal()); 62 | } 63 | 64 | // --- TEST CASES --- 65 | 66 | let id; 67 | const date = new Date(); 68 | 69 | // Count of posts 70 | checker.add("COUNT", () => broker.call("posts.count"), res => { 71 | console.log(res); 72 | return res == 0; 73 | }); 74 | 75 | // Create new Posts 76 | checker.add("--- CREATE ---", () => broker.call("posts.create", { title: "Hello", content: "Post content", votes: 2, createdAt: date, status: true }), doc => { 77 | id = doc._id; 78 | console.log("Saved: ", doc); 79 | return doc._id && doc.title === "Hello" && doc.content === "Post content" && doc.votes === 2 && doc.createdAt.getTime() === date.getTime(); 80 | }); 81 | 82 | // Find posts 83 | checker.add("--- FIND ---", () => broker.call("posts.find"), res => { 84 | console.log(res); 85 | return res.length == 1 && res[0]._id == id; 86 | }); 87 | 88 | // List posts 89 | checker.add("--- LIST ---", () => broker.call("posts.list"), res => { 90 | console.log(res); 91 | let rows = res.rows; 92 | return [ 93 | res.total === 1 && res.page === 1 && res.pageSize === 10 && res.totalPages === 1, 94 | rows.length == 1, 95 | rows[0]._id == id 96 | ]; 97 | }); 98 | 99 | // Get a post 100 | checker.add("--- GET ---", () => broker.call("posts.get", { id }), res => { 101 | console.log(res); 102 | return res._id == id; 103 | }); 104 | 105 | // Vote a post 106 | checker.add("--- VOTE ---", () => broker.call("posts.vote", { 107 | id 108 | }), res => { 109 | console.log(res); 110 | return res._id == id && res.votes === 3; 111 | }); 112 | 113 | // Update a posts 114 | checker.add("--- UPDATE ---", () => broker.call("posts.update", { 115 | id, 116 | title: "Hello 2", 117 | content: "Post content 2", 118 | updatedAt: new Date() 119 | }), doc => { 120 | console.log(doc); 121 | return doc._id && doc.title === "Hello 2" && doc.content === "Post content 2" && doc.votes === 3 && doc.updatedAt; 122 | }); 123 | 124 | // Get a post 125 | checker.add("--- GET ---", () => broker.call("posts.get", { id }), doc => { 126 | console.log(doc); 127 | return doc._id == id && doc.title == "Hello 2" && doc.votes === 3; 128 | }); 129 | 130 | // Get a post 131 | checker.add("--- GET[] mapping ---", () => broker.call("posts.get", { id: [id], mapping: true }), res => { 132 | console.log(res); 133 | let doc = res[id]; 134 | return doc && doc._id == id && doc.title == "Hello 2" && doc.votes === 3; 135 | }); 136 | 137 | // Unvote a post 138 | checker.add("--- UNVOTE ---", () => broker.call("posts.unvote", { 139 | id 140 | }), res => { 141 | console.log(res); 142 | return res._id == id && res.votes === 2; 143 | }); 144 | 145 | // Count of posts 146 | checker.add("--- COUNT ---", () => broker.call("posts.count"), res => { 147 | console.log(res); 148 | return res == 1; 149 | }); 150 | 151 | // Remove a post 152 | checker.add("--- REMOVE ---", () => broker.call("posts.remove", { id }), res => { 153 | console.log(res); 154 | return res == 1; 155 | }); 156 | 157 | // Count of posts 158 | checker.add("--- COUNT ---", () => broker.call("posts.count"), res => { 159 | console.log(res); 160 | return res == 0; 161 | }); 162 | 163 | broker.start().then(() => start()); 164 | -------------------------------------------------------------------------------- /packages/moleculer-db/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer-db 3 | * Copyright (c) 2019 MoleculerJS (https://github.com/moleculerjs/moleculer-db) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | module.exports = require("./src"); 10 | -------------------------------------------------------------------------------- /packages/moleculer-db/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "moleculer-db", 3 | "version": "0.8.29", 4 | "description": "Moleculer service to store entities in database", 5 | "main": "index.js", 6 | "types": "./moleculer-db.d.ts", 7 | "scripts": { 8 | "dev": "nodemon examples/index.js", 9 | "ci": "jest --watch", 10 | "test": "jest --coverage", 11 | "lint": "eslint --ext=.js src test", 12 | "deps": "npm-check -u", 13 | "readme": "node node_modules/moleculer-docgen/index.js -t README.md src/index.js", 14 | "coverall": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js" 15 | }, 16 | "keywords": [ 17 | "microservice", 18 | "moleculer" 19 | ], 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/moleculerjs/moleculer-db.git" 23 | }, 24 | "homepage": "https://github.com/moleculerjs/moleculer-db/tree/master/packages/moleculer-db#readme", 25 | "author": "MoleculerJS", 26 | "license": "MIT", 27 | "peerDependencies": { 28 | "moleculer": "^0.12.0 || ^0.13.0 || ^0.14.0 || ^0.15.0" 29 | }, 30 | "devDependencies": { 31 | "coveralls": "^3.1.1", 32 | "eslint": "^8.21.0", 33 | "fakerator": "^0.3.6", 34 | "jest": "^27.0.6", 35 | "jest-cli": "^27.0.6", 36 | "kleur": "^4.1.5", 37 | "lolex": "^6.0.0", 38 | "moleculer": "^0.14.22", 39 | "moleculer-docgen": "^0.3.0", 40 | "nodemon": "^2.0.19" 41 | }, 42 | "jest": { 43 | "testEnvironment": "node", 44 | "coveragePathIgnorePatterns": [ 45 | "/node_modules/", 46 | "/test/services/" 47 | ] 48 | }, 49 | "engines": { 50 | "node": ">= 8.x.x" 51 | }, 52 | "dependencies": { 53 | "@seald-io/nedb": "^3.0.0", 54 | "lodash": "^4.17.21" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /packages/moleculer-db/src/errors.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer-db 3 | * Copyright (c) 2019 MoleculerJS (https://github.com/moleculerjs/moleculer-db) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | const { MoleculerClientError } = require("moleculer").Errors; 10 | 11 | //const ERR_ENTITY_NOT_FOUND = "ERR_ENTITY_NOT_FOUND"; 12 | 13 | /** 14 | * Entity not found 15 | * 16 | * @class EntityNotFoundError 17 | * @extends {MoleculerClientError} 18 | */ 19 | class EntityNotFoundError extends MoleculerClientError { 20 | 21 | /** 22 | * Creates an instance of EntityNotFoundError. 23 | * 24 | * @param {any} ID of entity 25 | * 26 | * @memberOf EntityNotFoundError 27 | */ 28 | constructor(id) { 29 | super("Entity not found", 404, null, { 30 | id 31 | }); 32 | } 33 | } 34 | 35 | 36 | module.exports = { 37 | EntityNotFoundError 38 | 39 | //ERR_ENTITY_NOT_FOUND, 40 | }; 41 | -------------------------------------------------------------------------------- /packages/moleculer-db/src/memory-adapter.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer-db 3 | * Copyright (c) 2019 MoleculerJS (https://github.com/moleculerjs/moleculer-db) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | const _ = require("lodash"); 10 | const Datastore = require("@seald-io/nedb"); 11 | const util = require("util"); 12 | const {method} = require("lodash"); 13 | 14 | /** 15 | * NeDB adapter for `moleculer-db` 16 | * 17 | * @class MemoryDbAdapter 18 | */ 19 | class MemoryDbAdapter { 20 | 21 | /** 22 | * Creates an instance of MemoryDbAdapter. 23 | * 24 | * @param {Object} opts 25 | * @memberof MemoryDbAdapter 26 | */ 27 | constructor(opts) { 28 | this.opts = opts; 29 | } 30 | 31 | /** 32 | * Initialize adapter 33 | * 34 | * @param {ServiceBroker} broker 35 | * @param {Service} service 36 | * @memberof MemoryDbAdapter 37 | */ 38 | init(broker, service) { 39 | this.broker = broker; 40 | this.service = service; 41 | } 42 | 43 | /** 44 | * Connect to database 45 | * 46 | * @returns {Promise} 47 | * @memberof MemoryDbAdapter 48 | */ 49 | connect() { 50 | if(this.opts instanceof Datastore) 51 | this.db = this.opts; //use preconfigured datastore 52 | else 53 | this.db = new Datastore(this.opts); // in-memory 54 | 55 | ["loadDatabase", "insert", "findOne", "count", "remove", "ensureIndex", "removeIndex"].forEach(method => { 56 | this.db[method] = util.promisify(this.db[method]); 57 | }); 58 | 59 | const _update = this.db["update"]; 60 | 61 | this.db["update"] = util.promisify(function(...args) { 62 | return new Promise(() => { 63 | const cb = args.pop(); 64 | return _update.call(this, ...args, (err, ...results) => { 65 | return cb(err, results); 66 | }); 67 | }); 68 | }); 69 | 70 | return this.db.loadDatabase(); 71 | } 72 | 73 | /** 74 | * Disconnect from database 75 | * 76 | * @returns {Promise} 77 | * @memberof MemoryDbAdapter 78 | */ 79 | disconnect() { 80 | this.db = null; 81 | return Promise.resolve(); 82 | } 83 | 84 | /** 85 | * Find all entities by filters. 86 | * 87 | * Available filter props: 88 | * - limit 89 | * - offset 90 | * - sort 91 | * - search 92 | * - searchFields 93 | * - query 94 | * 95 | * @param {Object} filters 96 | * @returns {Promise} 97 | * @memberof MemoryDbAdapter 98 | */ 99 | find(filters) { 100 | return new Promise((resolve, reject) => { 101 | this.createCursor(filters).exec((err, docs) => { 102 | /* istanbul ignore next */ 103 | if (err) 104 | return reject(err); 105 | 106 | resolve(docs); 107 | }); 108 | 109 | }); 110 | } 111 | 112 | /** 113 | * Find an entity by query 114 | * 115 | * @param {Object} query 116 | * @returns {Promise} 117 | * @memberof MemoryDbAdapter 118 | */ 119 | findOne(query) { 120 | return this.db.findOne(query); 121 | } 122 | 123 | /** 124 | * Find an entity by ID 125 | * 126 | * @param {any} _id 127 | * @returns {Promise} 128 | * @memberof MemoryDbAdapter 129 | */ 130 | findById(_id) { 131 | return this.db.findOne({ _id }); 132 | } 133 | 134 | /** 135 | * Find all entites by IDs 136 | * 137 | * @param {Array} ids 138 | * @returns {Promise} 139 | * @memberof MemoryDbAdapter 140 | */ 141 | findByIds(ids) { 142 | return new Promise((resolve, reject) => { 143 | this.db.find({ _id: { $in: ids } }).exec((err, docs) => { 144 | /* istanbul ignore next */ 145 | if (err) 146 | return reject(err); 147 | 148 | resolve(docs); 149 | }); 150 | 151 | }); 152 | } 153 | 154 | /** 155 | * Get count of filtered entites 156 | * 157 | * Available filter props: 158 | * - search 159 | * - searchFields 160 | * - query 161 | * 162 | * @param {Object} [filters={}] 163 | * @returns {Promise} 164 | * @memberof MemoryDbAdapter 165 | */ 166 | count(filters = {}) { 167 | return new Promise((resolve, reject) => { 168 | this.createCursor(filters).exec((err, docs) => { 169 | /* istanbul ignore next */ 170 | if (err) 171 | return reject(err); 172 | 173 | resolve(docs.length); 174 | }); 175 | 176 | }); 177 | } 178 | 179 | /** 180 | * Insert an entity 181 | * 182 | * @param {Object} entity 183 | * @returns {Promise} 184 | * @memberof MemoryDbAdapter 185 | */ 186 | insert(entity) { 187 | return this.db.insert(entity); 188 | } 189 | 190 | /** 191 | * Insert multiple entities 192 | * 193 | * @param {Array} entities 194 | * @returns {Promise} 195 | * @memberof MemoryDbAdapter 196 | */ 197 | insertMany(entities) { 198 | return this.db.insert(entities); 199 | } 200 | 201 | /** 202 | * Update many entities by `query` and `update` 203 | * 204 | * @param {Object} query 205 | * @param {Object} update 206 | * @returns {Promise} 207 | * @memberof MemoryDbAdapter 208 | */ 209 | updateMany(query, update) { 210 | return this.db.update(query, update, { multi: true }).then(res => res[0]); 211 | } 212 | 213 | /** 214 | * Update an entity by ID 215 | * 216 | * @param {any} _id 217 | * @param {Object} update 218 | * @returns {Promise} 219 | * @memberof MemoryDbAdapter 220 | */ 221 | updateById(_id, update) { 222 | return this.db.update({ _id }, update, { returnUpdatedDocs: true }).then(res => res[1]); 223 | } 224 | 225 | /** 226 | * Remove many entities which are matched by `query` 227 | * 228 | * @param {Object} query 229 | * @returns {Promise} 230 | * @memberof MemoryDbAdapter 231 | */ 232 | removeMany(query) { 233 | return this.db.remove(query, { multi: true }); 234 | } 235 | 236 | /** 237 | * Remove an entity by ID 238 | * 239 | * @param {any} _id 240 | * @returns {Promise} 241 | * @memberof MemoryDbAdapter 242 | */ 243 | removeById(_id) { 244 | return this.db.remove({ _id }); 245 | } 246 | 247 | /** 248 | * Clear all entities from DB 249 | * 250 | * @returns {Promise} 251 | * @memberof MemoryDbAdapter 252 | */ 253 | clear() { 254 | return this.db.remove({}, { multi: true }); 255 | } 256 | 257 | /** 258 | * Convert DB entity to JSON object 259 | * 260 | * @param {any} entity 261 | * @returns {Object} 262 | * @memberof MemoryDbAdapter 263 | */ 264 | entityToObject(entity) { 265 | return entity; 266 | } 267 | 268 | /** 269 | * Add filters to query 270 | * 271 | * Available filters: 272 | * - search 273 | * - searchFields 274 | * - sort 275 | * - limit 276 | * - offset 277 | * - query 278 | * 279 | * @param {Object} params 280 | * @returns {Query} 281 | * @memberof MemoryDbAdapter 282 | */ 283 | createCursor(params) { 284 | if (params) { 285 | let q; 286 | 287 | // Text search 288 | if (_.isString(params.search) && params.search !== "") { 289 | let fields = []; 290 | if (params.searchFields) { 291 | fields = _.isString(params.searchFields) ? params.searchFields.split(" ") : params.searchFields; 292 | } 293 | 294 | q = this.db.find({ 295 | $where: function() { 296 | let item = this; 297 | if (fields.length > 0) 298 | item = _.pick(this, fields); 299 | 300 | const res = _.values(item).find(v => String(v).toLowerCase().indexOf(params.search.toLowerCase()) !== -1); 301 | 302 | return res != null; 303 | } 304 | }); 305 | } else { 306 | if (params.query) 307 | q = this.db.find(params.query); 308 | else 309 | q = this.db.find({}); 310 | } 311 | 312 | // Sort 313 | if (params.sort) { 314 | const sortFields = {}; 315 | params.sort.forEach(field => { 316 | if (field.startsWith("-")) 317 | sortFields[field.slice(1)] = -1; 318 | else 319 | sortFields[field] = 1; 320 | }); 321 | q.sort(sortFields); 322 | } 323 | 324 | // Limit 325 | if (_.isNumber(params.limit) && params.limit > 0) 326 | q.limit(params.limit); 327 | 328 | // Offset 329 | if (_.isNumber(params.offset) && params.offset > 0) 330 | q.skip(params.offset); 331 | 332 | return q; 333 | } 334 | 335 | return this.db.find({}); 336 | } 337 | 338 | /** 339 | * Transforms 'idField' into NeDB's '_id' 340 | * @param {Object} entity 341 | * @param {String} idField 342 | * @memberof MemoryDbAdapter 343 | * @returns {Object} Modified entity 344 | */ 345 | beforeSaveTransformID (entity, idField) { 346 | const newEntity = _.cloneDeep(entity); 347 | 348 | if (idField !== "_id" && entity[idField] !== undefined) { 349 | newEntity._id = newEntity[idField]; 350 | delete newEntity[idField]; 351 | } 352 | 353 | return newEntity; 354 | } 355 | 356 | /** 357 | * Transforms NeDB's '_id' into user defined 'idField' 358 | * @param {Object} entity 359 | * @param {String} idField 360 | * @memberof MemoryDbAdapter 361 | * @returns {Object} Modified entity 362 | */ 363 | afterRetrieveTransformID (entity, idField) { 364 | if (idField !== "_id") { 365 | entity[idField] = entity["_id"]; 366 | delete entity._id; 367 | } 368 | return entity; 369 | } 370 | } 371 | 372 | module.exports = MemoryDbAdapter; 373 | -------------------------------------------------------------------------------- /packages/moleculer-db/src/utils.js: -------------------------------------------------------------------------------- 1 | function copyFieldValueByPath(doc, paths, res, pathIndex = 0, cachePaths = []) { 2 | if (pathIndex < paths.length) { 3 | const path = paths[pathIndex]; 4 | if (Array.isArray(doc)) { 5 | cachePaths[cachePaths.length - 1].type = "array"; 6 | if (path === "$") { 7 | doc.forEach((item, itemIndex) => { 8 | copyFieldValueByPath(item, paths, res, pathIndex + 1, cachePaths.concat({path: itemIndex})); 9 | }); 10 | } else if (Object.prototype.hasOwnProperty.call(doc, path)) { 11 | copyFieldValueByPath(doc[path], paths, res, pathIndex + 1, cachePaths.concat({path: path})); 12 | } 13 | } else if (doc != null && Object.prototype.hasOwnProperty.call(doc, path)) { 14 | cachePaths.push({ path }); 15 | copyFieldValueByPath(doc[path], paths, res, pathIndex + 1, cachePaths); 16 | } 17 | } else { 18 | let obj = res; 19 | for (let i = 0; i < cachePaths.length - 1; i++) { 20 | const cachePath = cachePaths[i]; 21 | if (!Object.prototype.hasOwnProperty.call(obj, cachePath.path)) { 22 | if (cachePath.type === "array") { 23 | obj[cachePath.path] = []; 24 | } else { 25 | obj[cachePath.path] = {}; 26 | } 27 | } 28 | obj = obj[cachePath.path]; 29 | } 30 | obj[cachePaths[cachePaths.length - 1].path] = doc; 31 | } 32 | } 33 | 34 | exports.copyFieldValueByPath = copyFieldValueByPath; 35 | 36 | /** 37 | * Flattens a JavaScript object using dot notation while preserving arrays and non-plain objects 38 | * @param {Object} obj - The object to flatten 39 | * @param {String} [prefix=""] - The prefix to use for nested keys (used internally for recursion) 40 | * @returns {Object} A flattened object with dot notation keys 41 | * 42 | * @example 43 | * const input = { 44 | * name: "John", 45 | * address: { 46 | * street: "Main St", 47 | * location: { 48 | * city: "Boston", 49 | * country: "USA" 50 | * } 51 | * }, 52 | * account: { 53 | * createdAt: new Date("2024-01-01"), 54 | * settings: { 55 | * theme: null, 56 | * language: undefined 57 | * } 58 | * }, 59 | * scores: [85, 90, 95], 60 | * _id: ObjectId("507f1f77bcf86cd799439011") 61 | * }; 62 | * 63 | * // Returns: 64 | * // { 65 | * // "name": "John", 66 | * // "address.street": "Main St", 67 | * // "address.location.city": "Boston", 68 | * // "address.location.country": "USA", 69 | * // "account.createdAt": Date("2024-01-01T00:00:00.000Z"), 70 | * // "account.settings.theme": null, 71 | * // "account.settings.language": undefined, 72 | * // "scores": [85, 90, 95], 73 | * // "_id": ObjectId("507f1f77bcf86cd799439011") 74 | * // } 75 | */ 76 | function flatten(obj, prefix = "") { 77 | return Object.keys(obj).reduce((acc, key) => { 78 | const prefixedKey = prefix ? `${prefix}.${key}` : key; 79 | const value = obj[key]; 80 | 81 | // Check if value is a plain object (not array, not null, and constructor is Object) 82 | const isPlainObject = typeof value === "object" && value && value.constructor === Object; 83 | 84 | // If it's a plain object, flatten it recursively 85 | // Otherwise, keep the value as is (handles primitives, null, undefined, arrays, dates, ObjectId, etc.) 86 | return isPlainObject 87 | ? { ...acc, ...flatten(value, prefixedKey) } 88 | : { ...acc, [prefixedKey]: value }; 89 | }, {}); 90 | } 91 | 92 | exports.flatten = flatten; 93 | -------------------------------------------------------------------------------- /packages/moleculer-db/test/checker.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer-db 3 | * Copyright (c) 2019 MoleculerJS (https://github.com/moleculerjs/moleculer-db) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | const _ = require("lodash"); 10 | const kleur = require("kleur"); 11 | 12 | class ModuleChecker { 13 | 14 | constructor(okCount) { 15 | this.tests = []; 16 | this.okCount = okCount; 17 | this.ok = 0; 18 | this.fail = 0; 19 | } 20 | 21 | add(title, fn, cb) { 22 | this.tests.push(() => Promise.resolve(this.printTitle(title)).then(() => fn()).then(rsp => { 23 | let res = cb(rsp); 24 | if (Array.isArray(res)) 25 | res.map(r => this.checkValid(r)); 26 | else if (res != null) 27 | this.checkValid(res); 28 | })); 29 | } 30 | 31 | execute() { 32 | return this.tests.reduce((p, fn) => p.then(fn).catch(fn), Promise.resolve()); 33 | } 34 | 35 | printTitle(text) { 36 | console.log(); 37 | console.log(kleur.yellow().bold(`--- ${text} ---`)); 38 | } 39 | 40 | checkValid(cond) { 41 | let res = cond; 42 | if (_.isFunction(cond)) 43 | res = cond(); 44 | 45 | if (res) { 46 | this.ok++; 47 | console.log(kleur.bgGreen().yellow().bold("--- OK ---")); 48 | } else { 49 | this.fail++; 50 | console.log(kleur.bgRed().yellow().bold("!!! FAIL !!!")); 51 | } 52 | } 53 | 54 | printTotal() { 55 | console.log(); 56 | console.log(kleur.bgGreen().yellow().bold(`--- OK: ${this.ok} of ${this.okCount} ---`), this.fail > 0 ? " | " + kleur.bgRed().yellow().bold(`!!! FAIL: ${this.fail} !!!`) : ""); 57 | console.log(); 58 | } 59 | } 60 | 61 | 62 | module.exports = ModuleChecker; 63 | -------------------------------------------------------------------------------- /packages/moleculer-db/test/integration/crud.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { ServiceBroker } = require("moleculer"); 4 | const DbService = require("../../src"); 5 | const { EntityNotFoundError } = require("../../src/errors"); 6 | const Adapter = require("../../src/memory-adapter"); 7 | 8 | function protectReject(err) { 9 | if (err && err.stack) 10 | console.error(err.stack); 11 | expect(err).toBe(true); 12 | } 13 | 14 | function equalAtLeast(test, orig) { 15 | Object.keys(orig).forEach(key => { 16 | expect(test[key]).toEqual(orig[key]); 17 | }); 18 | } 19 | 20 | function equalID(test, orig) { 21 | expect(test._id).toEqual(orig._id); 22 | } 23 | 24 | describe("Test CRUD methods", () => { 25 | // Create broker 26 | let broker = new ServiceBroker({ 27 | logger: console, 28 | logLevel: "error" 29 | }); 30 | 31 | // Load my service 32 | broker.createService(DbService, Object.assign({ 33 | name: "posts", 34 | adapter: new Adapter(), 35 | })); 36 | 37 | beforeAll(() => { 38 | return broker.start().delay(1000); 39 | }); 40 | 41 | afterAll(() => { 42 | return broker.stop(); 43 | }); 44 | 45 | const posts = [ 46 | { title: "My first post", content: "This is the content", votes: 2}, 47 | { title: "Second post", content: "Waiting for the next...", votes: 5}, 48 | { title: "My last post", content: "This is the end! Good bye!", votes: 0} 49 | ]; 50 | 51 | it("should create a new entity", () => { 52 | return broker.call("posts.create", posts[0]).catch(protectReject).then(res => { 53 | expect(res).toBeDefined(); 54 | expect(res._id).toBeDefined(); 55 | posts[0]._id = res._id; 56 | 57 | equalAtLeast(res, posts[0]); 58 | }); 59 | }); 60 | 61 | 62 | it("should create multiple entities", () => { 63 | return broker.call("posts.insert", { entities: [posts[1], posts[2] ] }).catch(protectReject).then(res => { 64 | expect(res.length).toBe(2); 65 | 66 | posts[1]._id = res[0]._id; 67 | posts[2]._id = res[1]._id; 68 | 69 | equalAtLeast(res[0], posts[1]); 70 | equalAtLeast(res[1], posts[2]); 71 | }); 72 | }); 73 | 74 | it("should throw error is params is empty", () => { 75 | return broker.call("posts.insert", { }).then(protectReject).catch(res => { 76 | expect(res).toBeInstanceOf(Error); 77 | expect(res.name).toBe("MoleculerClientError"); 78 | expect(res.code).toBe(400); 79 | expect(res.message).toBe("Invalid request! The 'params' must contain 'entity' or 'entities'!"); 80 | expect(res.data).toBeUndefined(); 81 | 82 | }); 83 | }); 84 | 85 | it("should return with count of entities", () => { 86 | return broker.call("posts.count").catch(protectReject).then(res => { 87 | expect(res).toBe(3); 88 | }); 89 | }); 90 | 91 | it("should return with the entity by ID", () => { 92 | return broker.call("posts.get", { id: posts[1]._id }).catch(protectReject).then(res => { 93 | equalAtLeast(res, posts[1]); 94 | }); 95 | }); 96 | 97 | it("should throw error if entity not found", () => { 98 | return broker.call("posts.get", { id: 123123 }).then(protectReject).catch(res => { 99 | expect(res).toBeInstanceOf(EntityNotFoundError); 100 | expect(res.name).toBe("EntityNotFoundError"); 101 | expect(res.code).toBe(404); 102 | expect(res.message).toBe("Entity not found"); 103 | expect(res.data.id).toBe(123123); 104 | }); 105 | }); 106 | 107 | it("should return with multiple entity by IDs", () => { 108 | return broker.call("posts.get", { id: [posts[2]._id, posts[0]._id] }).catch(protectReject).then(res => { 109 | expect(res.length).toBe(2); 110 | expect(res[0]._id == posts[0]._id || res[0]._id == posts[2]._id); 111 | expect(res[1]._id == posts[0]._id || res[1]._id == posts[2]._id); 112 | }); 113 | }); 114 | 115 | it("should find filtered entities (search)", () => { 116 | return broker.call("posts.find", { search: "first" }).catch(protectReject).then(res => { 117 | expect(res.length).toBe(1); 118 | equalID(res[0], posts[0]); 119 | }); 120 | }); 121 | 122 | it("should update an entity", () => { 123 | return broker.call("posts.update", { 124 | id: posts[1]._id, 125 | title: "Other title", 126 | content: "Modify my content", 127 | votes: 8 128 | }).catch(protectReject).then(res => { 129 | expect(res._id).toEqual(posts[1]._id); 130 | expect(res.title).toEqual("Other title"); 131 | expect(res.content).toEqual("Modify my content"); 132 | expect(res.votes).toEqual(8); 133 | posts[1] = res; 134 | }); 135 | }); 136 | 137 | it("should find filtered entities (sort)", () => { 138 | return broker.call("posts.find", { sort: "-votes" }).catch(protectReject).then(res => { 139 | expect(res.length).toBe(3); 140 | 141 | equalID(res[0], posts[1]); 142 | equalID(res[1], posts[0]); 143 | equalID(res[2], posts[2]); 144 | }); 145 | }); 146 | 147 | it("should find filtered entities (limit, offset)", () => { 148 | return broker.call("posts.find", { sort: "votes", limit: "2", offset: 1 }).catch(protectReject).then(res => { 149 | expect(res.length).toBe(2); 150 | equalID(res[0], posts[0]); 151 | equalID(res[1], posts[1]); 152 | }); 153 | }); 154 | 155 | it("should find filtered entities (max limit)", () => { 156 | return broker.call("posts.find", { sort: "votes", limit: 999 }).catch(protectReject).then(res => { 157 | expect(res.length).toBe(3); 158 | }); 159 | }); 160 | 161 | it("should find filtered entities (search)", () => { 162 | return broker.call("posts.find", { search: "post", sort: "-votes" }).catch(protectReject).then(res => { 163 | expect(res.length).toBe(2); 164 | equalID(res[0], posts[0]); 165 | equalID(res[1], posts[2]); 166 | }); 167 | }); 168 | 169 | it("should find filtered entities (search)", () => { 170 | return broker.call("posts.find", { search: "post", searchFields: ["title"], sort: "-votes" }).catch(protectReject).then(res => { 171 | expect(res.length).toBe(2); 172 | equalID(res[0], posts[0]); 173 | equalID(res[1], posts[2]); 174 | }); 175 | }); 176 | 177 | it("should list paginated entities", () => { 178 | return broker.call("posts.list", { sort: "-votes" }).catch(protectReject).then(res => { 179 | expect(res.page).toBe(1); 180 | expect(res.pageSize).toBe(10); 181 | expect(res.total).toBe(3); 182 | expect(res.totalPages).toBe(1); 183 | 184 | expect(res.rows.length).toBe(3); 185 | equalID(res.rows[0], posts[1]); 186 | equalID(res.rows[1], posts[0]); 187 | equalID(res.rows[2], posts[2]); 188 | }); 189 | }); 190 | 191 | it("should list paginated entities (page 2 & search)", () => { 192 | return broker.call("posts.list", { page: 2, search: "post", searchFields: ["title"] }).catch(protectReject).then(res => { 193 | expect(res.page).toBe(2); 194 | expect(res.pageSize).toBe(10); 195 | expect(res.total).toBe(2); 196 | expect(res.totalPages).toBe(1); 197 | 198 | expect(res.rows.length).toBe(0); 199 | }); 200 | }); 201 | 202 | it("should list paginated entities (page, pageSize as strings)", () => { 203 | return broker.call("posts.list", { page: "1", pageSize: "2", }).catch(protectReject).then(res => { 204 | expect(res.page).toBe(1); 205 | expect(res.pageSize).toBe(2); 206 | expect(res.total).toBe(3); 207 | expect(res.totalPages).toBe(2); 208 | 209 | expect(res.rows.length).toBe(2); 210 | }); 211 | }); 212 | 213 | it("should remove entity by ID", () => { 214 | return broker.call("posts.remove", { id: posts[2]._id }).catch(protectReject).then(res => { 215 | expect(res).toBe(1); 216 | }); 217 | }); 218 | 219 | it("should throw 404 because entity is not exist (remove)", () => { 220 | return broker.call("posts.remove", { id: posts[2]._id }).then(protectReject).catch(res => { 221 | expect(res).toBeInstanceOf(EntityNotFoundError); 222 | expect(res.name).toBe("EntityNotFoundError"); 223 | expect(res.code).toBe(404); 224 | expect(res.message).toBe("Entity not found"); 225 | expect(res.data.id).toBe(posts[2]._id); 226 | }); 227 | }); 228 | 229 | it("should throw 404 because entity is not exist (update)", () => { 230 | return broker.call("posts.update", { id: posts[2]._id, name: "Adam" }).then(protectReject).catch(res => { 231 | expect(res).toBeInstanceOf(EntityNotFoundError); 232 | expect(res.name).toBe("EntityNotFoundError"); 233 | expect(res.code).toBe(404); 234 | expect(res.message).toBe("Entity not found"); 235 | expect(res.data.id).toBe(posts[2]._id); 236 | }); 237 | }); 238 | 239 | it("should return with count of entities", () => { 240 | return broker.call("posts.count").catch(protectReject).then(res => { 241 | expect(res).toBe(2); 242 | }); 243 | }); 244 | /* 245 | it("should remove all entities", () => { 246 | return broker.call("posts.clear").catch(protectReject).then(res => { 247 | expect(res).toBe(2); 248 | }); 249 | }); 250 | 251 | it("should return with count of entities", () => { 252 | return broker.call("posts.count").catch(protectReject).then(res => { 253 | expect(res).toBe(0); 254 | }); 255 | }); 256 | */ 257 | }); 258 | -------------------------------------------------------------------------------- /packages/moleculer-db/test/integration/populates.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { ServiceBroker } = require("moleculer"); 4 | const DbService = require("../../src"); 5 | const Adapter = require("../../src/memory-adapter"); 6 | 7 | function protectReject(err) { 8 | if (err && err.stack) { 9 | console.error(err); 10 | console.error(err.stack); 11 | } 12 | expect(err).toBe(true); 13 | } 14 | 15 | describe("Test populates feature", () => { 16 | // Create broker 17 | let broker = new ServiceBroker({ 18 | logger: console, 19 | logLevel: "error" 20 | }); 21 | 22 | // Load my service 23 | broker.createService(DbService, { 24 | name: "posts", 25 | adapter: new Adapter(), 26 | settings: { 27 | fields: ["_id", "title", "content", "author", "reviewer", "reviewerId", "liked"], 28 | populates: { 29 | "liked.by": "users.get", 30 | author: { 31 | action: "users.get" 32 | }, 33 | reviewer: { 34 | field: "reviewerId", 35 | action: "users.get" 36 | } 37 | } 38 | } 39 | }); 40 | 41 | // Load my service 42 | broker.createService(DbService, { 43 | name: "users", 44 | settings: { 45 | fields: ["_id", "group", "username", "name"], 46 | populates: { 47 | group: { 48 | action: "groups.get" 49 | }, 50 | } 51 | } 52 | }); 53 | 54 | // Load my service 55 | broker.createService(DbService, { 56 | name: "groups", 57 | settings: { 58 | fields: ["_id", "name"], 59 | } 60 | }); 61 | 62 | let posts = [ 63 | { title: "My first post", content: "This is the content", votes: 2}, 64 | { title: "Second post", content: "Waiting for the next...", votes: 0}, 65 | { title: "My last post", content: "This is the end! Good bye!", votes: 5} 66 | ]; 67 | 68 | let users = [ 69 | { username: "john", name: "John", password: "123456" }, 70 | { username: "jane", name: "Jane", password: "password" }, 71 | { username: "walter", name: "Walter", password: "H31s3nb3rg" } 72 | ]; 73 | 74 | let groups = [ 75 | { name: "groupA" }, 76 | { name: "groupB" }, 77 | { name: "groupC" } 78 | ]; 79 | 80 | beforeAll(() => { 81 | return broker.start().then(() => { 82 | return broker.call("groups.insert", { entities: groups }).then(res => { 83 | res.forEach((e, i) => groups[i]._id = e._id); 84 | 85 | users[0].group = res[0]._id; 86 | users[1].group = res[1]._id; 87 | users[2].group = res[2]._id; 88 | 89 | return broker.call("users.insert", { entities: users }).then(res => { 90 | res.forEach((e, i) => users[i]._id = e._id); 91 | 92 | posts[0].author = res[2]._id; 93 | posts[0].reviewerId = res[0]._id; 94 | posts[0].liked = {by: res[0]._id}; 95 | posts[1].author = res[0]._id; 96 | posts[1].reviewerId = res[0]._id; 97 | posts[1].liked = {by: res[0]._id}; 98 | posts[2].author = res[1]._id; 99 | posts[2].reviewerId = res[0]._id; 100 | posts[2].liked = {by: res[0]._id}; 101 | 102 | 103 | return broker.call("posts.insert", { entities: posts }).then(res => { 104 | res.forEach((e, i) => posts[i]._id = e._id); 105 | }); 106 | }); 107 | }); 108 | }); 109 | }); 110 | 111 | it("should return with count of entities", () => { 112 | return broker.call("posts.count").catch(protectReject).then(res => { 113 | expect(res).toBe(3); 114 | }); 115 | }); 116 | 117 | it("should return with the entity and populate the author, reviewerId", () => { 118 | return broker.call("posts.get", { id: posts[0]._id, populate: ["author"] }).catch(protectReject).then(res => { 119 | expect(res).toEqual({ 120 | "_id": posts[0]._id, 121 | "author": {"_id": users[2]._id, "name": "Walter", group:groups[2]._id, "username": "walter"}, 122 | "content": "This is the content", 123 | "title": "My first post", 124 | "reviewerId": users[0]._id, 125 | "liked": {by: users[0]._id} 126 | }); 127 | }); 128 | }); 129 | 130 | it("should return with multiple entities by IDs", () => { 131 | return broker.call("posts.get", { 132 | id: [posts[2]._id, posts[0]._id], 133 | populate: ["author"], 134 | fields: ["title", "author.name"] 135 | }).catch(protectReject).then(res => { 136 | expect(res).toEqual([ 137 | {"author": {"name": "Jane"}, "title": "My last post"}, 138 | {"author": {"name": "Walter"}, "title": "My first post"} 139 | ]); 140 | }); 141 | }); 142 | 143 | it("should return with multiple entities as Object", () => { 144 | return broker.call("posts.get", { 145 | id: [posts[2]._id, posts[0]._id], 146 | fields: ["title", "votes"], 147 | mapping: true 148 | }).catch(protectReject).then(res => { 149 | expect(res[posts[0]._id]).toEqual({"title": "My first post"}); 150 | expect(res[posts[2]._id]).toEqual({"title": "My last post"}); 151 | }); 152 | }); 153 | 154 | it("should return with the entity and populate the review instead of reviewerId", () => { 155 | return broker.call("posts.get", { id: posts[0]._id, populate: ["author","reviewer"] }).catch(protectReject).then(res => { 156 | expect(res).toEqual({ 157 | "_id": posts[0]._id, 158 | "author": {"_id": users[2]._id, group:groups[2]._id, "name": "Walter", "username": "walter"}, 159 | "reviewerId":users[0]._id, 160 | "reviewer": {"_id": users[0]._id, group:groups[0]._id, "name": users[0].name, "username":users[0].username}, 161 | "content": "This is the content", 162 | "title": "My first post", 163 | "liked": {by: users[0]._id} 164 | }); 165 | }); 166 | }); 167 | 168 | it("should deeply populate all groups", () => { 169 | return broker 170 | .call( 171 | "posts.get", 172 | { 173 | id: posts[0]._id, 174 | populate: ["author.group","reviewer.group", "liked.by.group", "title.invalid"] 175 | }) 176 | .catch(protectReject) 177 | .then(res => { 178 | expect(res).toEqual({ 179 | "_id": posts[0]._id, 180 | "author": {"_id": users[2]._id, "name": "Walter", "username": "walter", group:{"_id": groups[2]._id, "name": "groupC"}}, 181 | "reviewerId":users[0]._id, 182 | "reviewer": {"_id": users[0]._id, "name": users[0].name, "username":users[0].username, group:{"_id": groups[0]._id, "name": "groupA"}}, 183 | "content": "This is the content", 184 | "title": "My first post", 185 | "liked": {by: {"_id": users[0]._id, "name": users[0].name, "username":users[0].username, group:{"_id": groups[0]._id, "name": "groupA"}}} 186 | }); 187 | }); 188 | }); 189 | 190 | it("should deeply populate one group", () => { 191 | return broker.call("posts.get", { id: posts[0]._id, populate: ["author.invalid","reviewer.group", "title.invalid"] }).catch(protectReject).then(res => { 192 | expect(res).toEqual({ 193 | "_id": posts[0]._id, 194 | "author": {"_id": users[2]._id, "name": "Walter", "username": "walter", group:groups[2]._id}, 195 | "reviewerId":users[0]._id, 196 | "reviewer": {"_id": users[0]._id, "name": users[0].name, "username":users[0].username, group:{"_id": groups[0]._id, "name": "groupA"}}, 197 | "content": "This is the content", 198 | "title": "My first post", 199 | "liked": {by: users[0]._id} 200 | }); 201 | }); 202 | }); 203 | }); 204 | -------------------------------------------------------------------------------- /packages/moleculer-db/test/unit/errors.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | let errors = require("../../src/errors"); 4 | 5 | 6 | describe("Test Errors", () => { 7 | 8 | it("test EntityNotFoundError", () => { 9 | let err = new errors.EntityNotFoundError(123); 10 | expect(err).toBeDefined(); 11 | expect(err).toBeInstanceOf(Error); 12 | expect(err).toBeInstanceOf(errors.EntityNotFoundError); 13 | expect(err.code).toBe(404); 14 | expect(err.type).toBe(null); 15 | expect(err.name).toBe("EntityNotFoundError"); 16 | expect(err.message).toBe("Entity not found"); 17 | expect(err.data).toEqual({ 18 | id: 123 19 | }); 20 | }); 21 | 22 | }); -------------------------------------------------------------------------------- /packages/moleculer-db/test/unit/index.actions.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { ServiceBroker, Service, Context } = require("moleculer"); 4 | const { ValidationError } = require("moleculer").Errors; 5 | const DbService = require("../../src"); 6 | 7 | function protectReject(err) { 8 | if (err && err.stack) { 9 | console.error(err); 10 | console.error(err.stack); 11 | } 12 | expect(err).toBe(true); 13 | } 14 | 15 | describe("Test DbService actions", () => { 16 | const doc = { id : 1 }; 17 | const docs = [doc]; 18 | 19 | const adapter = { 20 | init: jest.fn(() => Promise.resolve()), 21 | connect: jest.fn(() => Promise.resolve()), 22 | disconnect: jest.fn(() => Promise.resolve()), 23 | find: jest.fn(() => Promise.resolve(docs)), 24 | findById: jest.fn(() => Promise.resolve(doc)), 25 | findByIds: jest.fn(() => Promise.resolve(docs)), 26 | count: jest.fn(() => Promise.resolve(3)), 27 | insert: jest.fn(() => Promise.resolve(doc)), 28 | insertMany: jest.fn(() => Promise.resolve(docs)), 29 | updateMany: jest.fn(() => Promise.resolve(docs)), 30 | updateById: jest.fn(() => Promise.resolve(doc)), 31 | removeMany: jest.fn(() => Promise.resolve(5)), 32 | removeById: jest.fn(() => Promise.resolve(3)), 33 | clear: jest.fn(() => Promise.resolve(3)), 34 | entityToObject: jest.fn(obj => obj), 35 | beforeSaveTransformID: jest.fn(obj => obj) 36 | }; 37 | 38 | const broker = new ServiceBroker({ logger: false, validation: false }); 39 | const service = broker.createService(DbService, { 40 | name: "store", 41 | adapter, 42 | }); 43 | 44 | service.sanitizeParams = jest.fn((ctx, p) => p); 45 | service.transformDocuments = jest.fn((ctx, params, docs) => Promise.resolve(docs)); 46 | 47 | service._find = jest.fn((ctx, p) => ctx); 48 | service._count = jest.fn((ctx, p) => ctx); 49 | service._list = jest.fn((ctx, p) => ctx); 50 | service._create = jest.fn((ctx, p) => ctx); 51 | service._insert = jest.fn((ctx, p) => ctx); 52 | service._get = jest.fn((ctx, p) => ctx); 53 | service._update = jest.fn((ctx, p) => ctx); 54 | service._remove = jest.fn((ctx, p) => ctx); 55 | 56 | it("should set default settings", () => { 57 | expect(service.adapter).toEqual(adapter); 58 | expect(service.settings).toEqual({ 59 | entityValidator: null, 60 | fields: null, 61 | excludeFields: null, 62 | idField: "_id", 63 | maxLimit: -1, 64 | maxPageSize: 100, 65 | pageSize: 10, 66 | populates: null, 67 | useDotNotation: false, 68 | cacheCleanEventType: "broadcast" 69 | }); 70 | }); 71 | 72 | it("should called the 'init' method of adapter", () => { 73 | expect(adapter.init).toHaveBeenCalledTimes(1); 74 | expect(adapter.init).toHaveBeenCalledWith(broker, service); 75 | }); 76 | 77 | it("should call the 'connect' method", () => { 78 | service.connect = jest.fn(() => Promise.resolve()); 79 | 80 | return broker.start().then(() => { 81 | expect(service.connect).toHaveBeenCalledTimes(1); 82 | }).catch(protectReject); 83 | }); 84 | 85 | it("should call the '_find' method", () => { 86 | service.sanitizeParams.mockClear(); 87 | service._find.mockClear(); 88 | const p = {}; 89 | 90 | return broker.call("store.find", p).catch(protectReject).then(ctx => { 91 | expect(service.sanitizeParams).toHaveBeenCalledTimes(1); 92 | expect(service.sanitizeParams).toHaveBeenCalledWith(ctx, p); 93 | 94 | expect(service._find).toHaveBeenCalledTimes(1); 95 | expect(service._find).toHaveBeenCalledWith(ctx, p); 96 | }); 97 | }); 98 | 99 | it("should call the '_find' method with params", () => { 100 | service._find.mockClear(); 101 | service.sanitizeParams.mockClear(); 102 | const p = { 103 | limit: 5, 104 | offset: "3" 105 | }; 106 | 107 | return broker.call("store.find", p).catch(protectReject).then(ctx => { 108 | expect(service.sanitizeParams).toHaveBeenCalledTimes(1); 109 | expect(service.sanitizeParams).toHaveBeenCalledWith(ctx, p); 110 | 111 | expect(service._find).toHaveBeenCalledTimes(1); 112 | expect(service._find).toHaveBeenCalledWith(ctx, p); 113 | 114 | }); 115 | }); 116 | 117 | it("should call the 'list' method", () => { 118 | service.sanitizeParams.mockClear(); 119 | service._list.mockClear(); 120 | const p = {}; 121 | 122 | return broker.call("store.list", p).catch(protectReject).then(ctx => { 123 | expect(service.sanitizeParams).toHaveBeenCalledTimes(1); 124 | expect(service.sanitizeParams).toHaveBeenCalledWith(ctx, p); 125 | 126 | expect(service._list).toHaveBeenCalledTimes(1); 127 | expect(service._list).toHaveBeenCalledWith(ctx, p); 128 | }); 129 | }); 130 | 131 | it("should call the 'count' method", () => { 132 | service.sanitizeParams.mockClear(); 133 | service._count.mockClear(); 134 | const p = {}; 135 | 136 | return broker.call("store.count", p).catch(protectReject).then(ctx => { 137 | expect(service.sanitizeParams).toHaveBeenCalledTimes(1); 138 | expect(service.sanitizeParams).toHaveBeenCalledWith(ctx, p); 139 | 140 | expect(service._count).toHaveBeenCalledTimes(1); 141 | expect(service._count).toHaveBeenCalledWith(ctx, p); 142 | }); 143 | }); 144 | 145 | it("should call the 'count' method with pagination params", () => { 146 | service.sanitizeParams.mockClear(); 147 | service._count.mockClear(); 148 | const p = { limit: 5, offset: 10 }; 149 | 150 | return broker.call("store.count", p).catch(protectReject).then(ctx => { 151 | expect(service.sanitizeParams).toHaveBeenCalledTimes(1); 152 | expect(service.sanitizeParams).toHaveBeenCalledWith(ctx, p); 153 | 154 | expect(service._count).toHaveBeenCalledTimes(1); 155 | expect(service._count).toHaveBeenCalledWith(ctx, p); 156 | }); 157 | }); 158 | 159 | it("should call the 'create' method", () => { 160 | service._create.mockClear(); 161 | const p = { name: "John Smith", age: 45 }; 162 | 163 | return broker.call("store.create", p).catch(protectReject).then(ctx => { 164 | expect(service._create).toHaveBeenCalledTimes(1); 165 | expect(service._create).toHaveBeenCalledWith(ctx, p); 166 | }); 167 | }); 168 | 169 | it("should call the 'insert' method", () => { 170 | service.sanitizeParams.mockClear(); 171 | service._insert.mockClear(); 172 | const p = { name: "John Smith", age: 45 }; 173 | 174 | return broker.call("store.insert", p).catch(protectReject).then(ctx => { 175 | // expect(service.sanitizeParams).toHaveBeenCalledTimes(1); 176 | // expect(service.sanitizeParams).toHaveBeenCalledWith(ctx, p); 177 | 178 | expect(service._insert).toHaveBeenCalledTimes(1); 179 | expect(service._insert).toHaveBeenCalledWith(ctx, p); 180 | }); 181 | }); 182 | 183 | it("should call the 'get' method", () => { 184 | service.sanitizeParams.mockClear(); 185 | service._get.mockClear(); 186 | const p = { id: 5 }; 187 | 188 | return broker.call("store.get", p).catch(protectReject).then(ctx => { 189 | expect(service.sanitizeParams).toHaveBeenCalledTimes(1); 190 | expect(service.sanitizeParams).toHaveBeenCalledWith(ctx, p); 191 | 192 | expect(service._get).toHaveBeenCalledTimes(1); 193 | expect(service._get).toHaveBeenCalledWith(ctx, p); 194 | }); 195 | }); 196 | 197 | it("should call the 'update' method", () => { 198 | service._update.mockClear(); 199 | const p = { id: 1, name: "John Smith", age: 45 }; 200 | 201 | return broker.call("store.update", p).catch(protectReject).then(ctx => { 202 | expect(service._update).toHaveBeenCalledTimes(1); 203 | expect(service._update).toHaveBeenCalledWith(ctx, p); 204 | }); 205 | }); 206 | 207 | it("should call the 'remove' method", () => { 208 | service.sanitizeParams.mockClear(); 209 | service._remove.mockClear(); 210 | const p = { id: 1 }; 211 | 212 | return broker.call("store.remove", p).catch(protectReject).then(ctx => { 213 | // expect(service.sanitizeParams).toHaveBeenCalledTimes(1); 214 | // expect(service.sanitizeParams).toHaveBeenCalledWith(ctx, p); 215 | 216 | expect(service._remove).toHaveBeenCalledTimes(1); 217 | expect(service._remove).toHaveBeenCalledWith(ctx, p); 218 | }); 219 | }); 220 | }); 221 | -------------------------------------------------------------------------------- /packages/moleculer-db/test/unit/memory-adapter.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { ServiceBroker } = require("moleculer"); 4 | const Adapter = require("../../src/memory-adapter"); 5 | 6 | const Datastore = require('@seald-io/nedb'); 7 | 8 | function protectReject(err) { 9 | if (err && err.stack) { 10 | console.error(err); 11 | console.error(err.stack); 12 | } 13 | expect(err).toBe(true); 14 | } 15 | 16 | describe("Test Adapter constructor", () => { 17 | it("should be created", () => { 18 | const adapter = new Adapter(); 19 | expect(adapter).toBeDefined(); 20 | }); 21 | 22 | it("should be created with opts", () => { 23 | const opts = {}; 24 | const adapter = new Adapter(opts); 25 | expect(adapter).toBeDefined(); 26 | expect(adapter.opts).toBe(opts); 27 | }); 28 | 29 | it('should use preconfigured Datastore', () => { 30 | const ds = new Datastore(); 31 | const adapter = new Adapter(ds); 32 | adapter.connect(); 33 | expect(adapter).toBeDefined(); 34 | expect(adapter.db).toBe(ds); 35 | }); 36 | 37 | }); 38 | 39 | describe("Test Adapter methods", () => { 40 | const broker = new ServiceBroker({ logger: false }); 41 | const service = broker.createService({ 42 | name: "test" 43 | }); 44 | 45 | const opts = {}; 46 | 47 | const adapter = new Adapter(opts); 48 | adapter.init(broker, service); 49 | 50 | it("should connect", () => { 51 | return expect(adapter.connect()).resolves.toBeUndefined(); 52 | }); 53 | 54 | const doc = { 55 | name: "Walter White", 56 | age: 48, 57 | email: "heisenberg@gmail.com" 58 | }; 59 | 60 | let savedDoc; 61 | 62 | it("should insert a document", () => { 63 | return adapter.insert(doc) 64 | .then(res => { 65 | expect(res).toEqual(Object.assign({}, doc, { _id: expect.any(String) })); 66 | savedDoc = res; 67 | }) 68 | .catch(protectReject); 69 | }); 70 | 71 | let multipleDocs; 72 | it("should insert multiple document", () => { 73 | return adapter.insertMany([{ name: "John Doe", c: true, age: 41 }, { name: "Jane Doe", b: "Hello", age: 35 }, { name: "Adam Smith", email: "adam.smith@gmail.com", age: 35 }]) 74 | .then(res => { 75 | expect(res.length).toBe(3); 76 | expect(res[0]._id).toBeDefined(); 77 | expect(res[0].name).toBe("John Doe"); 78 | expect(res[0].age).toBe(41); 79 | 80 | expect(res[1]._id).toBeDefined(); 81 | expect(res[1].name).toBe("Jane Doe"); 82 | expect(res[1].age).toBe(35); 83 | 84 | expect(res[2]._id).toBeDefined(); 85 | expect(res[2].name).toBe("Adam Smith"); 86 | expect(res[2].email).toBe("adam.smith@gmail.com"); 87 | expect(res[2].age).toBe(35); 88 | 89 | multipleDocs = res; 90 | }) 91 | .catch(protectReject); 92 | }); 93 | 94 | it("should find by ID", () => { 95 | return expect(adapter.findById(savedDoc._id)).resolves.toEqual(savedDoc); 96 | }); 97 | 98 | it("should find one", () => { 99 | return expect(adapter.findOne({ age: 48 })).resolves.toEqual(savedDoc); 100 | }); 101 | 102 | it("should find by multiple ID", () => { 103 | return expect(adapter.findByIds([multipleDocs[0]._id, multipleDocs[1]._id, ])).resolves.toEqual([multipleDocs[0], multipleDocs[1]]); 104 | }); 105 | 106 | it("should find all without filter", () => { 107 | return adapter.find().then(res => { 108 | expect(res.length).toBe(4); 109 | }).catch(protectReject); 110 | }); 111 | 112 | it("should find all 'name' with raw query", () => { 113 | return expect(adapter.find({ query: { name: "John Doe" }})).resolves.toEqual([multipleDocs[0]]); 114 | }); 115 | 116 | it("should find all 'age: 35'", () => { 117 | return adapter.find({ query: { age: 35 }}).then(res => { 118 | expect(res.length).toBe(2); 119 | expect(res[0].age).toEqual(35); 120 | expect(res[1].age).toEqual(35); 121 | 122 | }).catch(protectReject); 123 | }); 124 | 125 | it("should find all 'Doe'", () => { 126 | return adapter.find({ search: "Doe" }).then(res => { 127 | expect(res.length).toBe(2); 128 | expect(res[0].name).toMatch("Doe"); 129 | expect(res[1].name).toMatch("Doe"); 130 | 131 | }).catch(protectReject); 132 | }); 133 | 134 | it("should find all 'Doe' in filtered fields", () => { 135 | return adapter.find({ search: "Doe", searchFields: ["email"] }).then(res => { 136 | expect(res.length).toBe(0); 137 | }).catch(protectReject); 138 | }); 139 | 140 | it("should find all 'walter' in filtered fields", () => { 141 | return adapter.find({ search: "walter", searchFields: "email name" }).then(res => { 142 | expect(res.length).toBe(1); 143 | expect(res[0]).toEqual(savedDoc); 144 | 145 | }).catch(protectReject); 146 | }); 147 | 148 | it("should count all 'walter' in filtered fields", () => { 149 | return expect(adapter.count({ search: "walter", searchFields: "email name" })).resolves.toBe(1); 150 | }); 151 | 152 | it("should sort the result", () => { 153 | return expect(adapter.find({ sort: ["name"] })).resolves.toEqual([ 154 | multipleDocs[2], 155 | multipleDocs[1], 156 | multipleDocs[0], 157 | savedDoc, 158 | ]); 159 | }); 160 | 161 | it("should sort by two fields in array", () => { 162 | return expect(adapter.find({ sort: ["-age", "-name"] })).resolves.toEqual([ 163 | savedDoc, 164 | multipleDocs[0], 165 | multipleDocs[1], 166 | multipleDocs[2], 167 | ]); 168 | }); 169 | 170 | it("should limit & skip the result", () => { 171 | return expect(adapter.find({ sort: ["-age", "-name"], limit: 2, offset: 1 })).resolves.toEqual([ 172 | multipleDocs[0], 173 | multipleDocs[1], 174 | ]); 175 | }); 176 | 177 | it("should count all entities", () => { 178 | return expect(adapter.count()).resolves.toBe(4); 179 | }); 180 | 181 | it("should count filtered entities", () => { 182 | return expect(adapter.count({ query: { email: { $exists: true } }})).resolves.toBe(2); 183 | }); 184 | 185 | it("should update a document", () => { 186 | return expect(adapter.updateById(savedDoc._id, { $set: { e: 1234 } })).resolves.toEqual(Object.assign({}, savedDoc, { e: 1234 })); 187 | }); 188 | 189 | it("should update many documents", () => { 190 | return expect(adapter.updateMany({ age: 35 }, { $set: { gender: "male" } })).resolves.toBe(2); 191 | }); 192 | 193 | it("should remove by ID", () => { 194 | return expect(adapter.removeById(multipleDocs[0]._id)).resolves.toBe(1); 195 | }); 196 | 197 | it("should remove many documents", () => { 198 | return expect(adapter.removeMany({ name: { $regex: /Doe/ } })).resolves.toBe(1); 199 | }); 200 | 201 | it("should count all entities", () => { 202 | return expect(adapter.count()).resolves.toBe(2); 203 | }); 204 | 205 | it("should clear all documents", () => { 206 | return expect(adapter.clear()).resolves.toBe(2); 207 | }); 208 | 209 | it("should disconnect", () => { 210 | return expect(adapter.disconnect()).resolves.toBeUndefined(); 211 | }); 212 | }); 213 | -------------------------------------------------------------------------------- /packages/moleculer-db/test/unit/utils.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { flatten } = require("../../src/utils"); 4 | 5 | class ObjectId { 6 | constructor() { 7 | this.timestamp = Math.floor(Date.now() / 1000).toString(16).padStart(8, "0"); 8 | this.machineId = Math.floor(Math.random() * 16777216).toString(16).padStart(6, "0"); 9 | this.processId = Math.floor(Math.random() * 65536).toString(16).padStart(4, "0"); 10 | this.counter = Math.floor(Math.random() * 16777216).toString(16).padStart(6, "0"); 11 | } 12 | 13 | toString() { 14 | return this.timestamp + this.machineId + this.processId + this.counter; 15 | } 16 | 17 | getTimestamp() { 18 | return new Date(parseInt(this.timestamp, 16) * 1000); 19 | } 20 | } 21 | 22 | describe("Test Utils", () => { 23 | describe("flatten", () => { 24 | it("should properly flatten a given object", () => { 25 | const oid = new ObjectId(); 26 | const date = new Date("2024-01-01"); 27 | const obj = { 28 | name: "John", 29 | active: true, 30 | address: { 31 | street: "Main St", 32 | location: { 33 | city: "Boston", 34 | country: "USA" 35 | } 36 | }, 37 | account: { 38 | createdAt: date, 39 | identifier: oid, 40 | isSync: false, 41 | settings: { 42 | theme: null, 43 | language: undefined 44 | } 45 | }, 46 | scores: [85, 90, 95], 47 | }; 48 | 49 | const expected = { 50 | "name": "John", 51 | "active": true, 52 | "address.street": "Main St", 53 | "address.location.city": "Boston", 54 | "address.location.country": "USA", 55 | "account.createdAt": date, 56 | "account.identifier": oid, 57 | "account.settings.theme": null, 58 | "account.settings.language": undefined, 59 | "account.isSync": false, 60 | "scores": [85, 90, 95], 61 | }; 62 | 63 | expect(flatten(obj)).toEqual(expected); 64 | }); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /proposal/moleculer-db-adapter-knex/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | coverage/ 4 | npm-debug.log 5 | stats.json 6 | yarn-error.log -------------------------------------------------------------------------------- /proposal/moleculer-db-adapter-knex/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | # 0.x.x (2017-xx-xx) 3 | 4 | ## New 5 | 6 | ## Fixes 7 | 8 | ## Breaking changes 9 | 10 | ## Changes 11 | 12 | -------------------------------------------------- -------------------------------------------------------------------------------- /proposal/moleculer-db-adapter-knex/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 MoleculerJS 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 | -------------------------------------------------------------------------------- /proposal/moleculer-db-adapter-knex/README.md: -------------------------------------------------------------------------------- 1 | ![Moleculer logo](http://moleculer.services/images/banner.png) 2 | 3 | # [WIP] moleculer-db-adapter-knex [![NPM version](https://img.shields.io/npm/v/moleculer-db-adapter-knex.svg)](https://www.npmjs.com/package/moleculer-db-adapter-knex) 4 | 5 | Knex SQL adapter for Moleculer DB service. 6 | 7 | # Features 8 | 9 | # Install 10 | 11 | ```bash 12 | $ npm install moleculer-db-adapter-knex --save 13 | ``` 14 | 15 | # Usage 16 | 17 | 18 | 19 | 20 | 27 | 28 | 29 | 30 | # Settings 31 | 32 | 33 | 34 | 35 | 46 | 47 | # Actions 48 | 49 | 50 | 51 | 86 | 87 | # Methods 88 | 89 | 90 | 91 | 92 | 127 | 128 | # Test 129 | ``` 130 | $ npm test 131 | ``` 132 | 133 | In development with watching 134 | 135 | ``` 136 | $ npm run ci 137 | ``` 138 | 139 | # License 140 | The project is available under the [MIT license](https://tldrlegal.com/license/mit-license). 141 | 142 | # Contact 143 | Copyright (c) 2016-2024 MoleculerJS 144 | 145 | [![@moleculerjs](https://img.shields.io/badge/github-moleculerjs-green.svg)](https://github.com/moleculerjs) [![@MoleculerJS](https://img.shields.io/badge/twitter-MoleculerJS-blue.svg)](https://twitter.com/MoleculerJS) 146 | -------------------------------------------------------------------------------- /proposal/moleculer-db-adapter-knex/examples/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const moduleName = process.argv[2] || "simple"; 4 | process.argv.splice(2, 1); 5 | 6 | require("./" + moduleName); -------------------------------------------------------------------------------- /proposal/moleculer-db-adapter-knex/examples/simple/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | let { ServiceBroker } = require("moleculer"); 4 | let StoreService = require("../../../moleculer-db/index"); 5 | let ModuleChecker = require("../../../moleculer-db/test/checker"); 6 | let KnexAdapter = require("../../index"); 7 | 8 | const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); 9 | 10 | // Create broker 11 | let broker = new ServiceBroker({ 12 | logger: console, 13 | logLevel: "debug" 14 | }); 15 | const adapter = new KnexAdapter({ client: "sqlite", connection: { filename: ":memory:" }, useNullAsDefault: true}); 16 | 17 | // Load my service 18 | broker.createService(StoreService, { 19 | name: "posts", 20 | adapter, 21 | table: { 22 | name: "posts", 23 | builder(table) { 24 | console.log("Create table..."); 25 | table.increments("id"); 26 | table.string("title"); 27 | table.string("content"); 28 | table.integer("votes"); 29 | table.boolean("status"); 30 | table.timestamp("createdAt"); 31 | } 32 | }, 33 | settings: { 34 | idField: "id" 35 | }, 36 | 37 | afterConnected() { 38 | this.logger.info("Connected successfully"); 39 | 40 | return this.clear().then(() => { 41 | //this.adapter.collection.createIndex( { title: "text", content: "text" } ); 42 | }).then(() => start()); 43 | } 44 | }); 45 | 46 | const checker = new ModuleChecker(24); 47 | 48 | // Start checks 49 | function start() { 50 | return Promise.resolve() 51 | .then(() => delay(500)) 52 | .then(() => checker.execute()) 53 | .catch(console.error) 54 | .then(() => broker.stop()) 55 | .then(() => checker.printTotal()); 56 | } 57 | 58 | // --- TEST CASES --- 59 | 60 | let ids =[]; 61 | let date = new Date(); 62 | 63 | // Count of posts 64 | checker.add("COUNT", () => adapter.count(), res => { 65 | console.log(res); 66 | return res == 0; 67 | }); 68 | 69 | // Insert a new Post 70 | checker.add("INSERT", () => adapter.insert({ title: "Hello", content: "Post content", votes: 3, status: true, createdAt: date }), doc => { 71 | ids[0] = doc[0]; 72 | console.log("Saved: ", doc); 73 | return doc.id && doc.title === "Hello" && doc.content === "Post content" && doc.votes === 3 && doc.status === true && doc.createdAt === date; 74 | }); 75 | 76 | // Find 77 | checker.add("FIND", () => adapter.find(), res => { 78 | console.log(res); 79 | return res.length == 1 && res[0].id == ids[0]; 80 | }); 81 | 82 | // Find by ID 83 | checker.add("GET", () => adapter.findById(ids[0]), res => { 84 | console.log(res); 85 | return res.id == ids[0]; 86 | }); 87 | 88 | // Count of posts 89 | checker.add("COUNT", () => adapter.count(), res => { 90 | console.log(res); 91 | return res == 1; 92 | }); 93 | 94 | // Insert many new Posts 95 | checker.add("INSERT MANY", () => adapter.insertMany([ 96 | { title: "Second", content: "Second post content", votes: 8, status: true, createdAt: new Date() }, 97 | { title: "Last", content: "Last document", votes: 1, status: false, createdAt: new Date() } 98 | ]), docs => { 99 | console.log("Saved: ", docs); 100 | ids[1] = docs[0].id; 101 | ids[2] = docs[1].id; 102 | 103 | return [ 104 | docs.length == 2, 105 | ids[1] && docs[0].title === "Second" && docs[0].votes === 8, 106 | ids[1] && docs[1].title === "Last" && docs[1].votes === 1 && docs[1].status === false 107 | ]; 108 | }); 109 | 110 | // Count of posts 111 | checker.add("COUNT", () => adapter.count(), res => { 112 | console.log(res); 113 | return res == 3; 114 | }); 115 | 116 | // Find 117 | checker.add("FIND by query", () => adapter.find({ query: { title: "Last" } }), res => { 118 | console.log(res); 119 | return res.length == 1 && res[0].id == ids[2]; 120 | }); 121 | 122 | // Find 123 | checker.add("FIND by limit, sort, query", () => adapter.find({ limit: 1, sort: ["votes", "-title"], offset: 1 }), res => { 124 | console.log(res); 125 | return res.length == 1 && res[0].id == ids[0]; 126 | }); 127 | 128 | // Find 129 | checker.add("FIND by query ($gt)", () => adapter.find({ query: { votes: { $gt: 2 } } }), res => { 130 | console.log(res); 131 | return res.length == 2; 132 | }); 133 | 134 | // Find 135 | checker.add("COUNT by query ($gt)", () => adapter.count({ query: { votes: { $gt: 2 } } }), res => { 136 | console.log(res); 137 | return res == 2; 138 | }); 139 | 140 | // Find 141 | checker.add("FIND by text search", () => adapter.find({ search: "content" }), res => { 142 | console.log(res); 143 | return [ 144 | res.length == 2, 145 | res[0]._score < 1 && res[0].title === "Hello", 146 | res[1]._score < 1 && res[1].title === "Second" 147 | ]; 148 | }); 149 | 150 | // Find by IDs 151 | checker.add("GET BY IDS", () => adapter.findByIds([ids[2], ids[0]]), res => { 152 | console.log(res); 153 | return res.length == 2; 154 | }); 155 | 156 | // Update a posts 157 | checker.add("UPDATE", () => adapter.updateById(ids[2], { $set: { 158 | title: "Last 2", 159 | updatedAt: new Date(), 160 | status: true 161 | }}), doc => { 162 | console.log("Updated: ", doc); 163 | return doc.id && doc.title === "Last 2" && doc.content === "Last document" && doc.votes === 1 && doc.status === true && doc.updatedAt; 164 | }); 165 | 166 | // Update by query 167 | checker.add("UPDATE BY QUERY", () => adapter.updateMany({ votes: { $lt: 5 }}, { 168 | $set: { status: false } 169 | }), count => { 170 | console.log("Updated: ", count); 171 | return count == 2; 172 | }); 173 | 174 | // Remove by query 175 | checker.add("REMOVE BY QUERY", () => adapter.removeMany({ votes: { $lt: 5 }}), count => { 176 | console.log("Removed: ", count); 177 | return count == 2; 178 | }); 179 | 180 | // Count of posts 181 | checker.add("COUNT", () => adapter.count(), res => { 182 | console.log(res); 183 | return res == 1; 184 | }); 185 | 186 | // Remove by ID 187 | checker.add("REMOVE BY ID", () => adapter.removeById(ids[1]), doc => { 188 | console.log("Removed: ", doc); 189 | return doc && doc.id == ids[1]; 190 | }); 191 | 192 | // Count of posts 193 | checker.add("COUNT", () => adapter.count(), res => { 194 | console.log(res); 195 | return res == 0; 196 | }); 197 | 198 | // Clear 199 | checker.add("CLEAR", () => adapter.clear(), res => { 200 | console.log(res); 201 | return res == 0; 202 | }); 203 | 204 | broker.start(); 205 | -------------------------------------------------------------------------------- /proposal/moleculer-db-adapter-knex/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer-db-adapter-knex 3 | * Copyright (c) 2019 MoleculerJS (https://github.com/moleculerjs/moleculer-db) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | module.exports = require("./src"); 10 | -------------------------------------------------------------------------------- /proposal/moleculer-db-adapter-knex/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "moleculer-db-adapter-knex", 3 | "version": "0.0.0", 4 | "description": "[WIP] Knex SQL adapter for Moleculer DB service.", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "nodemon examples/index.js", 8 | "ci": "jest --watch", 9 | "test": "jest --coverage", 10 | "lint": "eslint --ext=.js src test", 11 | "deps": "npm-check -u", 12 | "readme": "node node_modules/moleculer-docgen/index.js -t README.md src/index.js", 13 | "coverall": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js" 14 | }, 15 | "private": true, 16 | "keywords": [ 17 | "microservice", 18 | "moleculer" 19 | ], 20 | "repository": { 21 | "type": "git", 22 | "url": "git@github.com:moleculerjs/moleculer-db.git" 23 | }, 24 | "homepage": "https://github.com/moleculerjs/moleculer-db/tree/master/packages/moleculer-db-adapter-knex#readme", 25 | "author": "MoleculerJS", 26 | "license": "MIT", 27 | "peerDependencies": { 28 | "moleculer": "^0.12.0" 29 | }, 30 | "devDependencies": { 31 | "benchmarkify": "2.1.0", 32 | "coveralls": "2.13.1", 33 | "eslint": "6.6.0", 34 | "jest": "20.0.4", 35 | "jest-cli": "20.0.4", 36 | "lolex": "2.1.2", 37 | "moleculer": "0.8.4", 38 | "moleculer-docgen": "0.2.1", 39 | "nodemon": "1.11.0", 40 | "npm-check": "5.4.5", 41 | "sqlite3": "3.1.8" 42 | }, 43 | "jest": { 44 | "testEnvironment": "node", 45 | "coveragePathIgnorePatterns": [ 46 | "/node_modules/", 47 | "/test/services/" 48 | ] 49 | }, 50 | "engines": { 51 | "node": ">= 6.x.x" 52 | }, 53 | "dependencies": { 54 | "knex": "0.19.5" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /proposal/moleculer-db-adapter-knex/src/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer-db-adapter-knex 3 | * Copyright (c) 2019 MoleculerJS (https://github.com/moleculerjs/moleculer-db) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | const _ = require("lodash"); 10 | const Knex = require("knex"); 11 | 12 | class KnexDbAdapter { 13 | 14 | /** 15 | * Creates an instance of KnexDbAdapter. 16 | * @param {any} opts 17 | * @param {any} opts2 18 | * 19 | * @memberof KnexDbAdapter 20 | */ 21 | constructor(opts, opts2) { 22 | this.opts = opts; 23 | this.opts2 = opts2; 24 | } 25 | 26 | /** 27 | * Initialize adapter 28 | * 29 | * @param {ServiceBroker} broker 30 | * @param {Service} service 31 | * 32 | * @memberof KnexDbAdapter 33 | */ 34 | init(broker, service) { 35 | this.broker = broker; 36 | this.service = service; 37 | 38 | if (!this.service.schema.table) { 39 | /* istanbul ignore next */ 40 | throw new Error("Missing `table` definition in schema of service!"); 41 | } 42 | this.idField = this.service.settings.idField || "id"; 43 | } 44 | 45 | /** 46 | * Connect to database 47 | * 48 | * @returns {Promise} 49 | * 50 | * @memberof KnexDbAdapter 51 | */ 52 | connect() { 53 | this.db = Knex(this.opts || {}, this.opts2); 54 | 55 | const tableDef = this.service.schema.table; 56 | if (_.isString(tableDef)) { 57 | this.table = tableDef; 58 | } else if (_.isObject(tableDef)) { 59 | this.table = tableDef.name; 60 | if (_.isFunction(tableDef.builder)) { 61 | return this.db.schema.createTableIfNotExists(this.table, tableDef.builder).then(res => { 62 | //this.table = this.db(tableName); 63 | }); 64 | } 65 | } 66 | 67 | return Promise.resolve(); 68 | /* 69 | let uri, opts; 70 | if (_.isObject(this.opts) && this.opts.uri != null) { 71 | uri = this.opts.uri; 72 | opts = Object.assign({ promiseLibrary: Promise }, this.opts.opts); 73 | } else { 74 | uri = this.opts; 75 | } 76 | 77 | return MongoClient.connect(uri, opts).then(db => { 78 | this.db = db; 79 | this.table = db.table(this.service.schema.table); 80 | 81 | 82 | this.db.on("disconnected", function mongoDisconnected() { 83 | this.service.logger.warn("Disconnected from MongoDB."); 84 | }.bind(this)); 85 | 86 | }); 87 | */ 88 | } 89 | 90 | /** 91 | * Disconnect from database 92 | * 93 | * @returns {Promise} 94 | * 95 | * @memberof KnexDbAdapter 96 | */ 97 | disconnect() { 98 | if (this.db) { 99 | //this.db.close(); 100 | } 101 | return Promise.resolve(); 102 | } 103 | 104 | /** 105 | * Find all entities by filters. 106 | * 107 | * Available filter props: 108 | * - limit 109 | * - offset 110 | * - sort 111 | * - search 112 | * - searchFields 113 | * - query 114 | * 115 | * @param {Object} filters 116 | * @returns {Promise} 117 | * 118 | * @memberof KnexDbAdapter 119 | */ 120 | find(filters) { 121 | return this.createCursor(filters, false); 122 | } 123 | 124 | /** 125 | * Find an entities by ID. 126 | * 127 | * @param {String} _id 128 | * @returns {Promise} Return with the found document. 129 | * 130 | * @memberof KnexDbAdapter 131 | */ 132 | findById(_id) { 133 | return this.db(this.table).where(this.idField, _id); 134 | } 135 | 136 | /** 137 | * Find any entities by IDs. 138 | * 139 | * @param {Array} idList 140 | * @returns {Promise} Return with the found documents in an Array. 141 | * 142 | * @memberof KnexDbAdapter 143 | */ 144 | findByIds(idList) { 145 | return this.db(this.table).where({ 146 | [this.idField]: { 147 | $in: idList 148 | } 149 | }); 150 | } 151 | 152 | /** 153 | * Get count of filtered entites. 154 | * 155 | * Available query props: 156 | * - search 157 | * - searchFields 158 | * - query 159 | * 160 | * @param {Object} [filters={}] 161 | * @returns {Promise} Return with the count of documents. 162 | * 163 | * @memberof KnexDbAdapter 164 | */ 165 | count(filters = {}) { 166 | return Promise.resolve(-1); 167 | //return this.createCursor(filters, true); 168 | } 169 | 170 | /** 171 | * Insert an entity. 172 | * 173 | * @param {Object} entity 174 | * @returns {Promise} Return with the inserted document. 175 | * 176 | * @memberof KnexDbAdapter 177 | */ 178 | insert(entity) { 179 | return this.db(this.table).insert(entity, "*").debug(); 180 | } 181 | 182 | /** 183 | * Insert many entities 184 | * 185 | * @param {Array} entities 186 | * @returns {Promise>} Return with the inserted documents in an Array. 187 | * 188 | * @memberof KnexDbAdapter 189 | */ 190 | insertMany(entities) { 191 | return this.db(this.table).insert(entities, "*"); 192 | } 193 | 194 | /** 195 | * Update many entities by `query` and `update` 196 | * 197 | * @param {Object} query 198 | * @param {Object} update 199 | * @returns {Promise} Return with the count of modified documents. 200 | * 201 | * @memberof KnexDbAdapter 202 | */ 203 | updateMany(query, update) { 204 | return this.db(this.table).where(query).update(update); 205 | } 206 | 207 | /** 208 | * Update an entity by ID and `update` 209 | * 210 | * @param {String} _id - ObjectID as hexadecimal string. 211 | * @param {Object} update 212 | * @returns {Promise} Return with the updated document. 213 | * 214 | * @memberof KnexDbAdapter 215 | */ 216 | updateById(_id, update) { 217 | return this.db(this.table).where(this.idField, _id).update(update); 218 | } 219 | 220 | /** 221 | * Remove entities which are matched by `query` 222 | * 223 | * @param {Object} query 224 | * @returns {Promise} Return with the count of deleted documents. 225 | * 226 | * @memberof KnexDbAdapter 227 | */ 228 | removeMany(query) { 229 | return this.db(this.table).where(query).del(); 230 | } 231 | 232 | /** 233 | * Remove an entity by ID 234 | * 235 | * @param {String} _id - ObjectID as hexadecimal string. 236 | * @returns {Promise} Return with the removed document. 237 | * 238 | * @memberof KnexDbAdapter 239 | */ 240 | removeById(_id) { 241 | return this.db(this.table).where(this.idField, _id).del(); 242 | } 243 | 244 | /** 245 | * Clear all entities from table 246 | * 247 | * @returns {Promise} 248 | * 249 | * @memberof KnexDbAdapter 250 | */ 251 | clear() { 252 | return this.db(this.table).del(); 253 | } 254 | 255 | /** 256 | * Convert DB entity to JSON object. It converts the `_id` to hexadecimal `String`. 257 | * 258 | * @param {Object} entity 259 | * @returns {Object} 260 | * @memberof KnexDbAdapter 261 | */ 262 | entityToObject(entity) { 263 | let json = Object.assign({}, entity); 264 | return json; 265 | } 266 | 267 | /** 268 | * Create a filtered cursor. 269 | * 270 | * Available filters in `params`: 271 | * - search 272 | * - sort 273 | * - limit 274 | * - offset 275 | * - query 276 | * 277 | * @param {Object} params 278 | * @param {Boolean} isCounting 279 | * @returns {MongoCursor} 280 | */ 281 | createCursor(params, isCounting) { 282 | if (params) { 283 | let q = this.db(this.table).where(params.query); 284 | 285 | // Full-text search 286 | /*if (_.isString(params.search) && params.search !== "") { 287 | q = this.db(this.table).where(Object.assign(params.query || {}, { 288 | $text: { 289 | $search: params.search 290 | } 291 | }), { _score: { $meta: "textScore" } }); 292 | q.sort({ 293 | _score: { 294 | $meta: "textScore" 295 | } 296 | }); 297 | } else {*/ 298 | // Sort 299 | if (params.sort) { 300 | this.transformSort(q, params.sort); 301 | } 302 | //} 303 | 304 | // Offset 305 | if (_.isNumber(params.offset) && params.offset > 0) 306 | q.offset(params.offset); 307 | 308 | // Limit 309 | if (_.isNumber(params.limit) && params.limit > 0) 310 | q.limit(params.limit); 311 | 312 | return q; 313 | } 314 | 315 | // If not params 316 | if (isCounting) 317 | return this.db(this.table).count(this.idField); 318 | else 319 | return this.db(this.table).where({}); 320 | } 321 | 322 | /** 323 | * Convert the `sort` param to a `sort` object to Mongo queries. 324 | * 325 | * @param {Cursor} q 326 | * @param {String|Array|Object} paramSort 327 | * @returns {Object} Return with a sort object like `{ "votes": 1, "title": -1 }` 328 | * @memberof KnexDbAdapter 329 | */ 330 | transformSort(q, paramSort) { 331 | let sort = paramSort; 332 | if (_.isString(sort)) 333 | sort = sort.replace(/,/, " ").split(" "); 334 | 335 | if (Array.isArray(sort)) { 336 | let sortObj = {}; 337 | sort.forEach(s => { 338 | if (s.startsWith("-")) 339 | sortObj[s.slice(1)] = -1; 340 | else 341 | sortObj[s] = 1; 342 | }); 343 | return sortObj; 344 | } 345 | 346 | if (_.isObject(sort)) { 347 | Object.keys(sort).forEach(key => { 348 | q.sort(key, sort[key] > 0 ? "asc" : "desc"); 349 | }); 350 | } 351 | 352 | return q; 353 | } 354 | 355 | } 356 | 357 | module.exports = KnexDbAdapter; 358 | -------------------------------------------------------------------------------- /proposal/moleculer-db-adapter-knex/test/unit/index.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { ServiceBroker } = require("moleculer"); 4 | //const MyService = require("../../src"); 5 | 6 | describe("Test MyService", () => { 7 | const broker = new ServiceBroker({ logger: false }); 8 | // const service = broker.createService(MyService); 9 | 10 | it("should be created", () => { 11 | // expect(service).toBeDefined(); 12 | }); 13 | /* 14 | it("should return with 'Hello Anonymous'", () => { 15 | return broker.call("knex.test").then(res => { 16 | expect(res).toBe("Hello Anonymous"); 17 | }); 18 | }); 19 | 20 | it("should return with 'Hello John'", () => { 21 | return broker.call("knex.test", { name: "John" }).then(res => { 22 | expect(res).toBe("Hello John"); 23 | }); 24 | }); 25 | */ 26 | }); 27 | 28 | -------------------------------------------------------------------------------- /readme-generator.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const path = require("path"); 4 | const glob = require("glob"); 5 | const markdownMagic = require("markdown-magic"); 6 | 7 | const readmePath = path.join(__dirname, "README.md"); 8 | console.log(readmePath); 9 | markdownMagic(readmePath, { 10 | 11 | transforms: { 12 | RENDERLIST: function(content, opts) { 13 | const folders = glob.sync(path.join(opts.folder, "*")); 14 | //console.log(folders); 15 | if (folders.length == 0) return " "; 16 | 17 | let table = [ 18 | "## " + opts.title, 19 | "| Name | Version | Description |", 20 | "| ---- | ------- | ----------- |" 21 | 22 | ]; 23 | folders.forEach(folder => { 24 | let name = path.basename(folder); 25 | let pkg = require(path.resolve(folder, "package.json")); 26 | 27 | let line = `| [${name}](/${opts.folder}/${name}#readme) | [![NPM version](https://img.shields.io/npm/v/${name}.svg)](https://www.npmjs.com/package/${name}) | ${pkg.description} |`; 28 | 29 | table.push(line); 30 | }); 31 | 32 | return table.join("\n"); 33 | } 34 | }, 35 | DEBUG: false 36 | 37 | }, () => { 38 | console.log("README.md generated!"); 39 | }); --------------------------------------------------------------------------------