├── .editorconfig
├── .eslintrc.js
├── .github
└── workflows
│ ├── benchmark.yml
│ ├── dependencies.yml
│ ├── integration.yml
│ ├── notification.yml
│ └── unit.yml
├── .gitignore
├── .npmignore
├── .vscode
├── launch.json
└── settings.json
├── CHANGELOG.md
├── LICENSE
├── README.md
├── TODO.md
├── benchmark
├── .eslintrc.js
├── generate-result.js
├── index.js
├── results
│ ├── common
│ │ ├── README.md
│ │ └── benchmark_results.json
│ └── transform
│ │ ├── README.md
│ │ └── benchmark_results.json
├── suites
│ ├── .gitignore
│ ├── common.js
│ ├── native-nedb.js
│ └── transform.js
└── utils.js
├── docs
├── README.md
└── adapters
│ ├── Knex.md
│ ├── MongoDB.md
│ └── NeDB.md
├── examples
├── connect
│ └── index.js
├── global-pool
│ └── index.js
├── index.js
├── knex-migration
│ ├── index.js
│ └── migrations
│ │ ├── 20210425191836_init.js
│ │ └── 20210425193759_author.js
├── many
│ └── index.js
├── multi-tenants
│ ├── .gitignore
│ ├── index.js
│ └── plenty.js
└── simple
│ └── index.js
├── index.js
├── package-lock.json
├── package.json
├── prettier.config.js
├── src
├── actions.js
├── adapters
│ ├── base.js
│ ├── index.js
│ ├── knex.js
│ ├── mongodb.js
│ └── nedb.js
├── constants.js
├── errors.js
├── index.js
├── methods.js
├── monitoring.js
├── schema.js
├── transform.js
├── utils.js
└── validation.js
└── test
├── docker-compose.yml
├── integration
├── actions.test.js
├── adapter.test.js
├── index.spec.js
├── methods.test.js
├── populate.test.js
├── rest.test.js
├── scopes.test.js
├── tenants.test.js
├── transform.test.js
├── utils.js
└── validation.test.js
├── leak-detection
├── index.spec.js
└── self-check.spec.js
├── scripts
├── mysql-create-databases.sql
└── pg-create-databases.sql
└── unit
├── schema.spec.js
├── scoping.spec.js
├── utils.spec.js
└── validation.spec.js
/.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 | trim_trailing_whitespace = false
22 | indent_style = space
23 | indent_size = 4
24 |
25 | [{package,bower}.json]
26 | indent_style = space
27 | indent_size = 2
28 |
29 | [*.{yml,yaml}]
30 | indent_style = space
31 | indent_size = 2
32 |
33 | [*.js]
34 | quote_type = "double"
35 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | node: true,
4 | commonjs: true,
5 | es6: true,
6 | jquery: false,
7 | jest: true,
8 | jasmine: true
9 | },
10 | extends: ["eslint:recommended", "plugin:security/recommended", "plugin:prettier/recommended"],
11 | parserOptions: {
12 | sourceType: "module",
13 | ecmaVersion: 2018
14 | },
15 | plugins: ["node", "promise", "security"],
16 | rules: {
17 | "no-var": ["error"],
18 | "no-console": ["warn"],
19 | "no-unused-vars": ["warn"],
20 | "no-trailing-spaces": ["error"],
21 | "security/detect-object-injection": ["off"],
22 | "security/detect-non-literal-require": ["off"],
23 | "security/detect-non-literal-fs-filename": ["off"],
24 | "no-process-exit": ["off"],
25 | "node/no-unpublished-require": 0,
26 | "require-atomic-updates": 0,
27 | "object-curly-spacing": ["warn", "always"]
28 | }
29 | };
30 |
--------------------------------------------------------------------------------
/.github/workflows/benchmark.yml:
--------------------------------------------------------------------------------
1 | name: Benchmark Test
2 |
3 | on: [workflow_dispatch]
4 |
5 | jobs:
6 | test:
7 | runs-on: ubuntu-latest
8 |
9 | steps:
10 | - uses: actions/checkout@v4
11 |
12 | - name: Use Node.js
13 | uses: actions/setup-node@v4
14 | with:
15 | node-version: 22.x
16 |
17 | - name: Install dependencies
18 | run: npm ci
19 |
20 | - name: Start containers
21 | run: docker compose up -d
22 | working-directory: ./test
23 |
24 | - name: Sleeping 30 secs
25 | run: sleep 30
26 |
27 | - name: Check containers
28 | run: docker compose ps
29 | working-directory: ./test
30 |
31 | - name: Run benchmark tests
32 | run: npm run bench
33 | timeout-minutes: 15
34 | env:
35 | GITHUB_ACTIONS_CI: true
36 |
37 | - name: Show container logs (in case of failure)
38 | run: docker compose logs
39 | if: failure()
40 | working-directory: ./test
41 |
42 | - name: Stop containers
43 | run: docker compose down -v
44 | working-directory: ./test
45 |
46 | - name: Commit the result
47 | if: success()
48 | uses: EndBug/add-and-commit@v7 # You can change this to use a specific version
49 | with:
50 | # The arguments for the `git add` command (see the paragraph below for more info)
51 | # Default: '.'
52 | add: './benchmark/results'
53 | default_author: github_actions
54 | push: true
55 |
--------------------------------------------------------------------------------
/.github/workflows/dependencies.yml:
--------------------------------------------------------------------------------
1 | name: Update dependencies
2 |
3 | on:
4 | schedule:
5 | - cron: '0 0 1 * *'
6 | workflow_dispatch:
7 | jobs:
8 | update-dependencies:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v4
12 | - name: Use Node.js
13 | uses: actions/setup-node@v4
14 | with:
15 | node-version: '22.x'
16 |
17 | - run: npm ci
18 |
19 | - run: |
20 | git config user.name "GitHub Actions Bot"
21 | git config user.email "hello@moleculer.services"
22 | git checkout -b update-deps-$GITHUB_RUN_ID
23 |
24 | - run: npm run ci-update-deps
25 | - run: npm i
26 | - run: npm audit fix || true
27 |
28 | - run: |
29 | git commit -am "Update dependencies (auto)"
30 | git push origin update-deps-$GITHUB_RUN_ID
31 | - run: |
32 | gh pr create --title "[deps]: Update all dependencies" --body ""
33 | env:
34 | GITHUB_TOKEN: ${{secrets.CI_ACCESS_TOKEN}}
35 |
--------------------------------------------------------------------------------
/.github/workflows/integration.yml:
--------------------------------------------------------------------------------
1 | name: Integration Test
2 |
3 | on:
4 | push:
5 | branches:
6 | - '**'
7 | paths-ignore:
8 | - '.vscode/**'
9 | - 'benchmark/**'
10 | - 'docs/**'
11 | - 'examples/**'
12 | - '*.md'
13 |
14 | pull_request:
15 | branches:
16 | - master
17 | paths-ignore:
18 | - '.vscode/**'
19 | - 'benchmark/**'
20 | - 'docs/**'
21 | - 'examples/**'
22 | - '*.md'
23 |
24 | jobs:
25 | test:
26 | runs-on: ubuntu-latest
27 |
28 | strategy:
29 | matrix:
30 | node-version: [20.x, 22.x, 24.x]
31 | adapter: ["NeDB", "MongoDB", "Knex-SQLite", "Knex-Postgresql", "Knex-MySQL", "Knex-MySQL2", "Knex-MSSQL"]
32 | fail-fast: false
33 | steps:
34 | - uses: actions/checkout@v4
35 |
36 | - name: Use Node.js ${{ matrix.node-version }}
37 | uses: actions/setup-node@v4
38 | with:
39 | node-version: ${{ matrix.node-version }}
40 |
41 | - name: Install dependencies
42 | run: npm ci
43 |
44 | - name: Start containers for ${{ matrix.adapter }} adapter
45 | run: docker compose up -d mongo && sleep 30
46 | if: ${{ matrix.adapter == 'MongoDB' }}
47 | working-directory: ./test
48 |
49 | - name: Start containers for ${{ matrix.adapter }} adapter
50 | run: docker compose up -d postgres && sleep 30
51 | if: ${{ matrix.adapter == 'Knex-Postgresql' }}
52 | working-directory: ./test
53 |
54 | - name: Start containers for ${{ matrix.adapter }} adapter
55 | run: docker compose up -d mysql && sleep 30
56 | if: ${{ matrix.adapter == 'Knex-MySQL' || matrix.adapter == 'Knex-MySQL2' }}
57 | working-directory: ./test
58 |
59 | - name: Start containers for ${{ matrix.adapter }} adapter
60 | run: docker compose up -d mssql mssql-create-db && sleep 60
61 | if: ${{ matrix.adapter == 'Knex-MSSQL' }}
62 | working-directory: ./test
63 |
64 | - name: Check containers
65 | run: docker compose ps
66 | working-directory: ./test
67 |
68 | - name: Check logs
69 | run: docker compose logs
70 | working-directory: ./test
71 |
72 | - name: Run integration tests
73 | run: npm run test:integration
74 | timeout-minutes: 10
75 | env:
76 | GITHUB_ACTIONS_CI: true
77 | ADAPTER: ${{ matrix.adapter }}
78 |
79 | # - name: Run leak detection tests
80 | # run: npm run test:leak
81 | # env:
82 | # GITHUB_ACTIONS_CI: true
83 |
84 | - name: Show container logs (in case of failure)
85 | run: docker compose logs
86 | if: failure()
87 | working-directory: ./test
88 |
89 | - name: Stop containers
90 | run: docker compose down -v
91 | working-directory: ./test
92 |
93 | # - name: Upload code coverage
94 | # run: npm run coverall
95 | # if: success() && github.ref == 'refs/heads/master'
96 | # env:
97 | # COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }}
98 |
--------------------------------------------------------------------------------
/.github/workflows/notification.yml:
--------------------------------------------------------------------------------
1 | # This is a basic workflow to help you get started with Actions
2 |
3 | name: Discord Notification
4 |
5 | on:
6 | release:
7 | types:
8 | - published
9 |
10 | jobs:
11 | notify:
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - name: Discord notification
16 | env:
17 | DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
18 | uses: Ilshidur/action-discord@master
19 | with:
20 | args: ":tada: **The {{ EVENT_PAYLOAD.repository.name }} {{ EVENT_PAYLOAD.release.tag_name }} has been released.**:tada:\nChangelog: {{EVENT_PAYLOAD.release.html_url}}"
21 |
--------------------------------------------------------------------------------
/.github/workflows/unit.yml:
--------------------------------------------------------------------------------
1 | name: Unit Test
2 |
3 | on:
4 | push:
5 | branches:
6 | - '**'
7 | paths-ignore:
8 | - '.vscode/**'
9 | - 'benchmark/**'
10 | - 'docs/**'
11 | - 'examples/**'
12 | - '*.md'
13 |
14 | pull_request:
15 | branches:
16 | - master
17 | paths-ignore:
18 | - '.vscode/**'
19 | - 'benchmark/**'
20 | - 'docs/**'
21 | - 'examples/**'
22 | - '*.md'
23 |
24 | jobs:
25 | test:
26 | runs-on: ubuntu-latest
27 |
28 | strategy:
29 | matrix:
30 | node-version: [20.x, 22.x, 24.x]
31 | fail-fast: false
32 | steps:
33 | - uses: actions/checkout@v4
34 |
35 | - name: Use Node.js ${{ matrix.node-version }}
36 | uses: actions/setup-node@v4
37 | with:
38 | node-version: ${{ matrix.node-version }}
39 |
40 | - name: Install dependencies
41 | run: npm ci
42 |
43 | - name: Run unit tests
44 | run: npm run test:unit
45 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # TypeScript v1 declaration files
45 | typings/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Microbundle cache
57 | .rpt2_cache/
58 | .rts2_cache_cjs/
59 | .rts2_cache_es/
60 | .rts2_cache_umd/
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env
73 | .env.test
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 |
78 | # Next.js build output
79 | .next
80 |
81 | # Nuxt.js build / generate output
82 | .nuxt
83 | dist
84 |
85 | # Gatsby files
86 | .cache/
87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
88 | # https://nextjs.org/blog/next-9-1#public-directory-support
89 | # public
90 |
91 | # vuepress build output
92 | .vuepress/dist
93 |
94 | # Serverless directories
95 | .serverless/
96 |
97 | # FuseBox cache
98 | .fusebox/
99 |
100 | # DynamoDB Local files
101 | .dynamodb/
102 |
103 | # TernJS port file
104 | .tern-port
105 |
106 | # Sqlite DB files
107 | *.db-journal
108 | *.sqlite3
109 | *.sqlite3-journal
110 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .vscode/
2 | .github/
3 | benchmark/
4 | coverage/
5 | dev/
6 | docs/
7 | example/
8 | examples/
9 | typings/
10 | test/
11 | *.db
12 | .editorconfig
13 | .eslintrc.js
14 | prettier.config.js
15 |
--------------------------------------------------------------------------------
/.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 | {
9 | "type": "node",
10 | "request": "launch",
11 | "name": "Launch demo",
12 | "program": "${workspaceRoot}/examples/index.js",
13 | "cwd": "${workspaceRoot}",
14 | "args": [
15 | "simple"
16 | ]
17 | },
18 | {
19 | "type": "node",
20 | "request": "launch",
21 | "name": "Launch selected demo",
22 | "program": "${file}",
23 | "cwd": "${workspaceRoot}"
24 | },
25 | {
26 | "type": "node",
27 | "request": "launch",
28 | "name": "Jest",
29 | "program": "${workspaceRoot}/node_modules/jest-cli/bin/jest.js",
30 | "args": ["--testMatch", "\"**/integration/**/*.spec.js\"", "--runInBand"],
31 | "cwd": "${workspaceRoot}",
32 | "runtimeArgs": [
33 | "--nolazy"
34 | ],
35 | "env": {
36 | "ADAPTER": "Knex-Postgresql"
37 | }
38 | },
39 | {
40 | "type": "node",
41 | "request": "launch",
42 | "name": "Jest single",
43 | "program": "${workspaceRoot}/node_modules/jest-cli/bin/jest.js",
44 | "args": ["--runInBand", "${fileBasenameNoExtension}"],
45 | "console": "internalConsole",
46 | "cwd": "${workspaceRoot}",
47 | "runtimeArgs": [
48 | "--nolazy"
49 | ]
50 | },
51 | ]
52 | }
53 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 |
3 | "jest.autoRun": "off"
4 | }
5 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # [0.3.0](https://github.com/moleculerjs/database/compare/v0.2.1...v0.3.0) (2025-05-31)
4 |
5 | - update dependencies.
6 | - Minimum node version is 20.
7 | - switch from `nedb` to `@seald-io/nedb` to support Node 24+.
8 | -
9 |
10 |
11 | # [0.2.1](https://github.com/moleculerjs/database/compare/v0.2.0...v0.2.1) (2024-07-28)
12 |
13 | - update dependencies
14 | - add moleculer 0.15 peer dependency
15 |
16 |
17 |
18 | # [0.2.0](https://github.com/moleculerjs/database/compare/v0.1.1...v0.2.0) (2024-04-01)
19 |
20 | **Breaking changes**
21 | - upgrade `knex` to 3.1.0
22 | - upgrade `mongodb` to 6.5.0
23 | - minimum Node version bumped to 18
24 |
25 |
26 |
27 | # [0.1.1](https://github.com/moleculerjs/database/compare/v0.1.0...v0.1.1) (2023-04-23)
28 |
29 | - fix permission/permissive type [#31](https://github.com/moleculerjs/moleculer-channels/pull/31)
30 | - fix TypeError `createFromHexString` [#40](https://github.com/moleculerjs/moleculer-channels/pull/40)
31 | - fix waiting for adapter in connecting state [#2d9888e](https://github.com/moleculerjs/database/commit/2d9888e497363ac88aa3b62c354d680d53b3213b)
32 |
33 |
34 |
35 | # v0.1.0 (2022-10-02)
36 |
37 | First public version.
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 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 | 
2 |
3 | 
4 | [](https://coveralls.io/github/moleculerjs/database?branch=master)
5 | [](https://snyk.io/test/github/moleculerjs/database)
6 | [](https://www.npmjs.com/package/@moleculer/database)
7 |
8 | # @moleculer/database
9 | Advanced Database Access Service for Moleculer microservices framework. Use it to persist your data in a database.
10 |
11 | >this module follows the *one database per service* pattern. To learn more about this design pattern and its implications check this [article](https://microservices.io/patterns/data/database-per-service.html). For *multiple entities/tables per service* approach check [FAQ](https://moleculer.services/docs/0.14/faq.html#How-can-I-manage-multiple-entities-tables-per-service).
12 |
13 | ## Features
14 | - multiple pluggable adapters (NeDB, MongoDB, Knex)
15 | - common CRUD actions for RESTful API with caching
16 | - pagination, field filtering support
17 | - field sanitizations, validations
18 | - read-only, immutable, virtual fields
19 | - field permissions (read/write)
20 | - ID field encoding
21 | - data transformation
22 | - populating between Moleculer services
23 | - create/update/remove hooks
24 | - soft delete mode
25 | - scopes support
26 | - entity lifecycle events
27 | - Multi-tenancy
28 |
29 | ## Install
30 | ```
31 | npm i @moleculer/database @seald-io/nedb
32 | ```
33 | > Installing `@seald-io/nedb` is optional. It can be good for prototyping.
34 |
35 | ## Usage
36 |
37 | **Define the service**
38 | ```js
39 | // posts.service.js
40 |
41 | const DbService = require("@moleculer/database").Service;
42 |
43 | module.exports = {
44 | name: "posts",
45 | mixins: [
46 | DbService({
47 | adapter: "NeDB"
48 | })
49 | ],
50 |
51 | settings: {
52 | fields: {
53 | id: { type: "string", primaryKey: true, columnName: "_id" },
54 | title: { type: "string", max: 255, trim: true, required: true },
55 | content: { type: "string" },
56 | votes: { type: "number", integer: true, min: 0, default: 0 },
57 | status: { type: "boolean", default: true },
58 | createdAt: { type: "number", readonly: true, onCreate: () => Date.now() },
59 | updatedAt: { type: "number", readonly: true, onUpdate: () => Date.now() }
60 | }
61 | }
62 | }
63 | ```
64 |
65 | **Call the actions**
66 | ```js
67 | // sample.js
68 |
69 | // Create a new post
70 | let post = await broker.call("posts.create", {
71 | title: "My first post",
72 | content: "Content of my first post..."
73 | });
74 | console.log("New post:", post);
75 | /* Results:
76 | New post: {
77 | id: 'Zrpjq8B1XTSywUgT',
78 | title: 'My first post',
79 | content: 'Content of my first post...',
80 | votes: 0,
81 | status: true,
82 | createdAt: 1618065551990
83 | }
84 | */
85 |
86 | // Get all posts
87 | let posts = await broker.call("posts.find", { sort: "-createdAt" });
88 | console.log("Find:", posts);
89 |
90 | // List posts with pagination
91 | posts = await broker.call("posts.list", { page: 1, pageSize: 10 });
92 | console.log("List:", posts);
93 |
94 | // Get a post by ID
95 | post = await broker.call("posts.get", { id: post.id });
96 | console.log("Get:", post);
97 |
98 | // Update the post
99 | post = await broker.call("posts.update", { id: post.id, title: "Modified post" });
100 | console.log("Updated:", post);
101 |
102 | // Delete a user
103 | const res = await broker.call("posts.remove", { id: post.id });
104 | console.log("Deleted:", res);
105 | ```
106 |
107 | [**Try it in your browser on repl.it**](https://replit.com/@icebob/moleculer-database-common)
108 |
109 | ## Documentation
110 | You can find [here the documentation](docs/README.md).
111 |
112 | ## Benchmark
113 | There is some benchmark with all adapters. [You can find the results here.](benchmark/results/common/README.md)
114 |
115 | ## License
116 | The project is available under the [MIT license](https://tldrlegal.com/license/mit-license).
117 |
118 | ## Contact
119 | Copyright (c) 2025 MoleculerJS
120 |
121 | [](https://github.com/moleculerjs) [](https://twitter.com/MoleculerJS)
122 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | # TODO
2 |
3 | - [ ] **More integration tests**
4 | - [ ] createIndex, removeIndex
5 | - [ ] Knex createCursor operators `$`
6 | - [ ] global pool handling
7 | - [x] soft delete
8 |
9 | ## Actions
10 | - [ ] `aggregate` action with params: `type: "sum", "avg", "count", "min", "max"` & `field: "price"`
11 |
12 | ## Fields
13 | - [ ] `hidden: "inLists"`
14 | - [ ] using projections, get only required fields in adapters.
15 | - [ ] `projection: []` for getter/setters
16 |
17 | ## Methods
18 | - [x] wrap the args to obj in custom functions: `({ ctx, id, field })`
19 | - [ ] same for `get`
20 | - [ ] multiple get option for `get` in transform (same as populating to avoid hundreds sub-calls)
21 | - [x] if `field.validate` is string, call the method by name
22 | - [ ] same for `get`
23 | - [x] same for `set`
24 | - [ ] option to disable validators `opts.validation: false`
25 | - [x] add `scope` param for `removeEntities` and `updateEntities` and pass to the `findEntities` call inside the method.
26 | - [x] skipping softDelete in methods `opts.softDelete: false` to make real delete in maintenance processes
27 | - [ ] Add `events: false` to options to disable entity changed events. (e.g. when `softDelete: false` we don't want to send event about changes)
28 |
29 |
30 | ## Just if I bored to death
31 | - [ ] generate OpenAPI schema
32 | - [ ] `introspect: true`
33 | - [ ] create REST API to get the field definitions, and actions with params
34 | - [ ] Generate sample data based on fields with Fakerator. `username: { type: "string", fake: "entity.user.userName" }`
35 | - [ ] validate `raw: true` update fields (`$set`, `$inc`)
36 | - [ ] client-side (Vue) module which can communicate with service via REST or GraphQL.
37 | - [ ] ad-hoc populate in find/list actions `populate: ["author", { key: "createdBy", action: "users.resolve", fields: ["name", "avatar"] }]` { }
38 | - [ ] auto revision handling (`_rev: 1`). Autoincrement on every update/replace and always checks the rev value to avoid concurrent updating.
39 | - [ ]
40 |
--------------------------------------------------------------------------------
/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/generate-result.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const fs = require("fs");
4 | const path = require("path");
5 | const globby = require("globby");
6 | const humanize = require("tiny-human-time");
7 | const { saveMarkdown, createChartURL, numToStr, makeTableRow } = require("./utils");
8 |
9 | async function generateMarkdown(folderName) {
10 | const folder = path.join(__dirname, "results", folderName);
11 | const files = await globby(["*.json"], { cwd: folder });
12 | if (files.length == 0) return;
13 |
14 | console.log("Found files:", files);
15 |
16 | const results = files
17 | .map(filename => fs.readFileSync(path.join(folder, filename), "utf8"))
18 | .map(content => JSON.parse(content));
19 |
20 | const rows = ["", ""];
21 |
22 | for (const result of results) {
23 | console.log("Process test:", result.name);
24 |
25 | rows.push(`# ${result.name}`);
26 | rows.push(`${result.description || {}}`);
27 |
28 | if (result.meta.adapters) {
29 | rows.push(
30 | "## Test configurations",
31 | "",
32 | "| Name | Adapter | Options |",
33 | "| ---- | ------- | ------- |"
34 | );
35 | for (const adapter of result.meta.adapters) {
36 | let options = JSON.stringify(adapter.options);
37 | if (options) {
38 | //options = options.replace(/\n/g, "
").replace(/ /g, " ");
39 | options = "`" + options + "`";
40 | }
41 | rows.push(
42 | makeTableRow([adapter.name || adapter.type, adapter.type, options || "-"])
43 | );
44 | }
45 | }
46 |
47 | for (const suite of result.suites) {
48 | rows.push(`## ${suite.name}`);
49 | if (suite.meta && suite.meta.description) rows.push(suite.meta.description);
50 | rows.push("", "### Result");
51 | rows.push("");
52 |
53 | rows.push(
54 | "",
55 | "| Adapter config | Time | Diff | ops/sec |",
56 | "| -------------- | ----:| ----:| -------:|"
57 | );
58 |
59 | suite.tests.forEach(test => {
60 | rows.push(
61 | makeTableRow([
62 | test.name,
63 | humanize.short(test.stat.avg * 1000),
64 | numToStr(test.stat.percent) + "%",
65 | numToStr(test.stat.rps)
66 | ])
67 | );
68 | });
69 | rows.push("");
70 |
71 | rows.push(
72 | "`,
76 | chf: "b0,lg,90,03a9f4,0,3f51b5,1",
77 | chg: "0,50",
78 | chma: "0,0,10,10",
79 | cht: "bvs",
80 | chxt: "x,y",
81 | chxs: "0,333,10|1,333,10",
82 |
83 | chxl: "0:|" + suite.tests.map(s => s.name).join("|"),
84 | chd: "a:" + suite.tests.map(s => s.stat.rps).join(",")
85 | }) +
86 | ")"
87 | );
88 | rows.push("");
89 | }
90 | }
91 |
92 | rows.push("--------------------");
93 | rows.push(`_Generated at ${new Date().toISOString()}_`);
94 |
95 | const filename = path.join(folder, "README.md");
96 | console.log("Write to file...");
97 | saveMarkdown(filename, rows);
98 |
99 | console.log("Done. Result:", filename);
100 | }
101 |
102 | module.exports = { generateMarkdown };
103 |
104 | //generateMarkdown("common");
105 |
--------------------------------------------------------------------------------
/benchmark/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | require("./suites/" + (process.argv[2] || "common"));
--------------------------------------------------------------------------------
/benchmark/results/common/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Moleculer Database benchmark - Common
4 | This is a common benchmark which create, list, get, update, replace and delete entities via service actions.
5 | ## Test configurations
6 |
7 | | Name | Adapter | Options |
8 | | ---- | ------- | ------- |
9 | | NeDB (memory) | NeDB | - |
10 | | NeDB (file) | NeDB | `"/home/runner/work/database/database/benchmark/suites/tmp/common.db"` |
11 | | MongoDB | MongoDB | `{"dbName":"bench_test","collection":"users"}` |
12 | | Knex SQLite (memory) | Knex | `{"knex":{"client":"sqlite3","connection":{"filename":":memory:"},"useNullAsDefault":true,"log":{}}}` |
13 | | Knex SQLite (file) | Knex | `{"knex":{"client":"sqlite3","connection":{"filename":"/home/runner/work/database/database/benchmark/suites/tmp/common.sqlite3"},"useNullAsDefault":true,"pool":{"min":1,"max":1},"log":{}}}` |
14 | | Knex-Postgresql | Knex | `{"knex":{"client":"pg","connection":{"host":"127.0.0.1","port":5432,"user":"postgres","password":"moleculer","database":"bench_test"}}}` |
15 | | Knex-MySQL | Knex | `{"knex":{"client":"mysql","connection":{"host":"127.0.0.1","user":"root","password":"moleculer","database":"bench_test"},"log":{}}}` |
16 | | Knex-MySQL2 | Knex | `{"knex":{"client":"mysql2","connection":{"host":"127.0.0.1","user":"root","password":"moleculer","database":"bench_test"},"log":{}}}` |
17 | | Knex-MSSQL | Knex | `{"knex":{"client":"mssql","connection":{"host":"127.0.0.1","port":1433,"user":"sa","password":"Moleculer@Pass1234","database":"bench_test","encrypt":false}}}` |
18 | ## Entity creation
19 |
20 | ### Result
21 |
22 |
23 | | Adapter config | Time | Diff | ops/sec |
24 | | -------------- | ----:| ----:| -------:|
25 | | NeDB (memory) | 73μs | 728.68% | 13,586.12 |
26 | | NeDB (file) | 229μs | 165.51% | 4,353.07 |
27 | | MongoDB | 609μs | 0% | 1,639.5 |
28 | | Knex SQLite (memory) | 476μs | 27.9% | 2,096.89 |
29 | | Knex SQLite (file) | 2ms | -71.43% | 468.47 |
30 | | Knex-Postgresql | 1ms | -61% | 639.48 |
31 | | Knex-MySQL | 2ms | -73.05% | 441.89 |
32 | | Knex-MySQL2 | 2ms | -71.85% | 461.47 |
33 | | Knex-MSSQL | 3ms | -82.74% | 282.95 |
34 |
35 | 
36 |
37 | ## Entity finding
38 |
39 | ### Result
40 |
41 |
42 | | Adapter config | Time | Diff | ops/sec |
43 | | -------------- | ----:| ----:| -------:|
44 | | NeDB (memory) | 290μs | 248.58% | 3,438.72 |
45 | | NeDB (file) | 286μs | 253.88% | 3,491.05 |
46 | | MongoDB | 1ms | 0% | 986.5 |
47 | | Knex SQLite (memory) | 412μs | 145.77% | 2,424.54 |
48 | | Knex SQLite (file) | 404μs | 150.31% | 2,469.34 |
49 | | Knex-Postgresql | 717μs | 41.2% | 1,392.97 |
50 | | Knex-MySQL | 989μs | 2.4% | 1,010.17 |
51 | | Knex-MySQL2 | 914μs | 10.85% | 1,093.54 |
52 | | Knex-MSSQL | 1ms | -23.76% | 752.09 |
53 |
54 | 
55 |
56 | ## Entity listing
57 |
58 | ### Result
59 |
60 |
61 | | Adapter config | Time | Diff | ops/sec |
62 | | -------------- | ----:| ----:| -------:|
63 | | NeDB (memory) | 1ms | 30.07% | 646.22 |
64 | | NeDB (file) | 1ms | 26.12% | 626.57 |
65 | | MongoDB | 2ms | 0% | 496.82 |
66 | | Knex SQLite (memory) | 581μs | 246.1% | 1,719.49 |
67 | | Knex SQLite (file) | 572μs | 251.74% | 1,747.53 |
68 | | Knex-Postgresql | 1ms | 70.99% | 849.53 |
69 | | Knex-MySQL | 2ms | -10.87% | 442.81 |
70 | | Knex-MySQL2 | 1ms | 4.68% | 520.05 |
71 | | Knex-MSSQL | 2ms | -1.95% | 487.15 |
72 |
73 | 
74 |
75 | ## Entity counting
76 |
77 | ### Result
78 |
79 |
80 | | Adapter config | Time | Diff | ops/sec |
81 | | -------------- | ----:| ----:| -------:|
82 | | NeDB (memory) | 1ms | -13.77% | 831.2 |
83 | | NeDB (file) | 1ms | -15.89% | 810.78 |
84 | | MongoDB | 1ms | 0% | 963.92 |
85 | | Knex SQLite (memory) | 143μs | 625.02% | 6,988.64 |
86 | | Knex SQLite (file) | 151μs | 583.66% | 6,589.99 |
87 | | Knex-Postgresql | 440μs | 135.65% | 2,271.52 |
88 | | Knex-MySQL | 1ms | -5.48% | 911.13 |
89 | | Knex-MySQL2 | 1ms | -4.57% | 919.91 |
90 | | Knex-MSSQL | 855μs | 21.28% | 1,169.05 |
91 |
92 | 
93 |
94 | ## Entity getting
95 |
96 | ### Result
97 |
98 |
99 | | Adapter config | Time | Diff | ops/sec |
100 | | -------------- | ----:| ----:| -------:|
101 | | NeDB (memory) | 38μs | 1,551.67% | 26,201.46 |
102 | | NeDB (file) | 39μs | 1,484.88% | 25,141.91 |
103 | | MongoDB | 630μs | 0% | 1,586.36 |
104 | | Knex SQLite (memory) | 222μs | 183.11% | 4,491.16 |
105 | | Knex SQLite (file) | 218μs | 189.08% | 4,585.79 |
106 | | Knex-Postgresql | 531μs | 18.59% | 1,881.31 |
107 | | Knex-MySQL | 559μs | 12.65% | 1,786.96 |
108 | | Knex-MySQL2 | 518μs | 21.46% | 1,926.84 |
109 | | Knex-MSSQL | 839μs | -24.91% | 1,191.25 |
110 |
111 | 
112 |
113 | ## Entity resolving
114 |
115 | ### Result
116 |
117 |
118 | | Adapter config | Time | Diff | ops/sec |
119 | | -------------- | ----:| ----:| -------:|
120 | | NeDB (memory) | 38μs | 1,534.53% | 25,873.6 |
121 | | NeDB (file) | 39μs | 1,519.21% | 25,631.06 |
122 | | MongoDB | 631μs | 0% | 1,582.93 |
123 | | Knex SQLite (memory) | 200μs | 214.6% | 4,979.87 |
124 | | Knex SQLite (file) | 210μs | 200.73% | 4,760.43 |
125 | | Knex-Postgresql | 572μs | 10.38% | 1,747.2 |
126 | | Knex-MySQL | 627μs | 0.65% | 1,593.23 |
127 | | Knex-MySQL2 | 506μs | 24.77% | 1,974.99 |
128 | | Knex-MSSQL | 895μs | -29.47% | 1,116.44 |
129 |
130 | 
131 |
132 | ## Entity updating
133 |
134 | ### Result
135 |
136 |
137 | | Adapter config | Time | Diff | ops/sec |
138 | | -------------- | ----:| ----:| -------:|
139 | | NeDB (memory) | 147μs | 750.83% | 6,798.18 |
140 | | NeDB (file) | 326μs | 282.97% | 3,060 |
141 | | MongoDB | 1ms | 0% | 799.01 |
142 | | Knex SQLite (memory) | 610μs | 104.98% | 1,637.85 |
143 | | Knex SQLite (file) | 1ms | -21.92% | 623.89 |
144 | | Knex-Postgresql | 1ms | -34.87% | 520.37 |
145 | | Knex-MySQL | 2ms | -50.09% | 398.78 |
146 | | Knex-MySQL2 | 2ms | -43.65% | 450.23 |
147 | | Knex-MSSQL | 3ms | -64.19% | 286.15 |
148 |
149 | 
150 |
151 | ## Entity replacing
152 |
153 | ### Result
154 |
155 |
156 | | Adapter config | Time | Diff | ops/sec |
157 | | -------------- | ----:| ----:| -------:|
158 | | NeDB (memory) | 126μs | 901.68% | 7,925.66 |
159 | | NeDB (file) | 310μs | 307.32% | 3,222.89 |
160 | | MongoDB | 1ms | 0% | 791.24 |
161 | | Knex SQLite (memory) | 605μs | 108.63% | 1,650.73 |
162 | | Knex SQLite (file) | 1ms | -22.23% | 615.35 |
163 | | Knex-Postgresql | 1ms | -36.42% | 503.1 |
164 | | Knex-MySQL | 2ms | -46.99% | 419.45 |
165 | | Knex-MySQL2 | 2ms | -40.83% | 468.15 |
166 | | Knex-MSSQL | 3ms | -68.36% | 250.36 |
167 |
168 | 
169 |
170 | ## Entity deleting
171 |
172 | ### Result
173 |
174 |
175 | | Adapter config | Time | Diff | ops/sec |
176 | | -------------- | ----:| ----:| -------:|
177 | | NeDB (memory) | 136μs | 148.38% | 7,309.39 |
178 | | NeDB (file) | 219μs | 54.97% | 4,560.63 |
179 | | MongoDB | 339μs | 0% | 2,942.84 |
180 | | Knex SQLite (memory) | 567μs | -40.12% | 1,762.03 |
181 | | Knex SQLite (file) | 1ms | -68.58% | 924.67 |
182 | | Knex-Postgresql | 263μs | 29.09% | 3,798.79 |
183 | | Knex-MySQL | 505μs | -32.83% | 1,976.77 |
184 | | Knex-MySQL2 | 307μs | 10.42% | 3,249.56 |
185 | | Knex-MSSQL | 1ms | -67.57% | 954.26 |
186 |
187 | 
188 |
189 | --------------------
190 | _Generated at 2022-09-02T19:29:11.443Z_
--------------------------------------------------------------------------------
/benchmark/results/transform/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Moleculer Database benchmark - Transformation benchmark
4 | This is a transformation benchmark. It tests all service methods with and without transformation.
5 | ## Entity creation
6 |
7 | ### Result
8 |
9 |
10 | | Adapter config | Time | Diff | ops/sec |
11 | | -------------- | ----:| ----:| -------:|
12 | | Without transform | 55μs | 0% | 18,168.9 |
13 | | With transform | 52μs | 4.99% | 19,076.02 |
14 |
15 | 
16 |
17 | ## Entity listing
18 |
19 | ### Result
20 |
21 |
22 | | Adapter config | Time | Diff | ops/sec |
23 | | -------------- | ----:| ----:| -------:|
24 | | Without transform | 131μs | 0% | 7,591.59 |
25 | | With transform | 148μs | -11.38% | 6,727.93 |
26 |
27 | 
28 |
29 | ## Entity counting
30 |
31 | ### Result
32 |
33 |
34 | | Adapter config | Time | Diff | ops/sec |
35 | | -------------- | ----:| ----:| -------:|
36 | | Without transform | 726μs | 0% | 1,376.87 |
37 |
38 | 
39 |
40 | ## Entity getting
41 |
42 | ### Result
43 |
44 |
45 | | Adapter config | Time | Diff | ops/sec |
46 | | -------------- | ----:| ----:| -------:|
47 | | Without transform | 21μs | 0% | 46,501.41 |
48 | | With transform | 28μs | -25.63% | 34,581.01 |
49 | | Direct adapter access | 20μs | 7.33% | 49,908.36 |
50 |
51 | 
52 |
53 | ## Entity updating
54 |
55 | ### Result
56 |
57 |
58 | | Adapter config | Time | Diff | ops/sec |
59 | | -------------- | ----:| ----:| -------:|
60 | | Without transform | 93μs | 0% | 10,694.34 |
61 | | With transform | 99μs | -6.38% | 10,011.73 |
62 |
63 | 
64 |
65 | ## Entity replacing
66 |
67 | ### Result
68 |
69 |
70 | | Adapter config | Time | Diff | ops/sec |
71 | | -------------- | ----:| ----:| -------:|
72 | | Without transform | 93μs | 0% | 10,662.15 |
73 | | With transform | 100μs | -7.11% | 9,904.25 |
74 |
75 | 
76 |
77 | ## Entity deleting
78 |
79 | ### Result
80 |
81 |
82 | | Adapter config | Time | Diff | ops/sec |
83 | | -------------- | ----:| ----:| -------:|
84 | | Without transform | 51μs | 0% | 19,603.08 |
85 | | With transform | 183μs | -72.15% | 5,459.5 |
86 |
87 | 
88 |
89 | --------------------
90 | _Generated at 2021-05-10T16:10:04.759Z_
--------------------------------------------------------------------------------
/benchmark/results/transform/benchmark_results.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Moleculer Database benchmark - Transformation benchmark",
3 | "description": "This is a transformation benchmark. It tests all service methods with and without transformation.",
4 | "meta": {},
5 | "suites": [
6 | {
7 | "name": "Entity creation",
8 | "tests": [
9 | {
10 | "name": "Without transform",
11 | "meta": {},
12 | "reference": true,
13 | "stat": {
14 | "duration": 5.0085585,
15 | "cycle": 0,
16 | "count": 91000,
17 | "avg": 0.0000550391043956044,
18 | "rps": 18168.900293367842,
19 | "percent": 0
20 | }
21 | },
22 | {
23 | "name": "With transform",
24 | "meta": {},
25 | "fastest": true,
26 | "stat": {
27 | "duration": 5.0324965,
28 | "cycle": 0,
29 | "count": 96000,
30 | "avg": 0.000052421838541666664,
31 | "rps": 19076.019228229965,
32 | "percent": 4.992701375510578
33 | }
34 | }
35 | ]
36 | },
37 | {
38 | "name": "Entity listing",
39 | "tests": [
40 | {
41 | "name": "Without transform",
42 | "meta": {},
43 | "fastest": true,
44 | "reference": true,
45 | "stat": {
46 | "duration": 5.005538,
47 | "cycle": 0,
48 | "count": 38000,
49 | "avg": 0.00013172468421052632,
50 | "rps": 7591.591553195681,
51 | "percent": 0
52 | }
53 | },
54 | {
55 | "name": "With transform",
56 | "meta": {},
57 | "stat": {
58 | "duration": 5.0535583,
59 | "cycle": 0,
60 | "count": 34000,
61 | "avg": 0.00014863406764705883,
62 | "rps": 6727.932672707071,
63 | "percent": -11.376519329797873
64 | }
65 | }
66 | ]
67 | },
68 | {
69 | "name": "Entity counting",
70 | "tests": [
71 | {
72 | "name": "Without transform",
73 | "meta": {},
74 | "fastest": true,
75 | "reference": true,
76 | "stat": {
77 | "duration": 5.0839816,
78 | "cycle": 0,
79 | "count": 7000,
80 | "avg": 0.0007262830857142858,
81 | "rps": 1376.8735905731837,
82 | "percent": 0
83 | }
84 | }
85 | ]
86 | },
87 | {
88 | "name": "Entity getting",
89 | "tests": [
90 | {
91 | "name": "Without transform",
92 | "meta": {},
93 | "reference": true,
94 | "stat": {
95 | "duration": 5.0106006,
96 | "cycle": 0,
97 | "count": 233000,
98 | "avg": 0.000021504723605150215,
99 | "rps": 46501.41142760411,
100 | "percent": 0
101 | }
102 | },
103 | {
104 | "name": "With transform",
105 | "meta": {},
106 | "stat": {
107 | "duration": 5.0316638,
108 | "cycle": 0,
109 | "count": 174000,
110 | "avg": 0.00002891760804597701,
111 | "rps": 34581.00678348184,
112 | "percent": -25.634500713339833
113 | }
114 | },
115 | {
116 | "name": "Direct adapter access",
117 | "meta": {},
118 | "fastest": true,
119 | "stat": {
120 | "duration": 5.0091804,
121 | "cycle": 0,
122 | "count": 250000,
123 | "avg": 0.0000200367216,
124 | "rps": 49908.36425056682,
125 | "percent": 7.326557879359939
126 | }
127 | }
128 | ]
129 | },
130 | {
131 | "name": "Entity updating",
132 | "tests": [
133 | {
134 | "name": "Without transform",
135 | "meta": {},
136 | "fastest": true,
137 | "reference": true,
138 | "stat": {
139 | "duration": 5.0494017,
140 | "cycle": 0,
141 | "count": 54000,
142 | "avg": 0.00009350743888888888,
143 | "rps": 10694.336321073446,
144 | "percent": 0
145 | }
146 | },
147 | {
148 | "name": "With transform",
149 | "meta": {},
150 | "stat": {
151 | "duration": 5.094025,
152 | "cycle": 0,
153 | "count": 51000,
154 | "avg": 0.00009988284313725491,
155 | "rps": 10011.729428104494,
156 | "percent": -6.382882232942848
157 | }
158 | }
159 | ]
160 | },
161 | {
162 | "name": "Entity replacing",
163 | "tests": [
164 | {
165 | "name": "Without transform",
166 | "meta": {},
167 | "fastest": true,
168 | "reference": true,
169 | "stat": {
170 | "duration": 5.0646435,
171 | "cycle": 0,
172 | "count": 54000,
173 | "avg": 0.00009378969444444445,
174 | "rps": 10662.152232432549,
175 | "percent": 0
176 | }
177 | },
178 | {
179 | "name": "With transform",
180 | "meta": {},
181 | "stat": {
182 | "duration": 5.0483396,
183 | "cycle": 0,
184 | "count": 50000,
185 | "avg": 0.000100966792,
186 | "rps": 9904.24653682173,
187 | "percent": -7.108374360904264
188 | }
189 | }
190 | ]
191 | },
192 | {
193 | "name": "Entity deleting",
194 | "tests": [
195 | {
196 | "name": "Without transform",
197 | "meta": {},
198 | "fastest": true,
199 | "reference": true,
200 | "stat": {
201 | "duration": 9.7211788,
202 | "cycle": 0,
203 | "count": 190565,
204 | "avg": 0.000051012404166557344,
205 | "rps": 19603.075297822932,
206 | "percent": 0
207 | }
208 | },
209 | {
210 | "name": "With transform",
211 | "meta": {},
212 | "stat": {
213 | "duration": 5.4950117,
214 | "cycle": 0,
215 | "count": 30000,
216 | "avg": 0.00018316705666666667,
217 | "rps": 5459.497019815262,
218 | "percent": -72.14979314790685
219 | }
220 | }
221 | ]
222 | }
223 | ],
224 | "timestamp": 1620663004740,
225 | "generated": "Mon May 10 2021 18:10:04 GMT+0200 (GMT+02:00)",
226 | "elapsedMs": 75379
227 | }
--------------------------------------------------------------------------------
/benchmark/suites/.gitignore:
--------------------------------------------------------------------------------
1 | tmp/
2 |
--------------------------------------------------------------------------------
/benchmark/suites/native-nedb.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const Datastore = require("nedb");
4 |
5 | const Fakerator = require("fakerator");
6 | const fakerator = new Fakerator();
7 |
8 | const Benchmarkify = require("benchmarkify");
9 | const benchmark = new Benchmarkify("NeDB benchmark").printHeader();
10 |
11 | const COUNT = 1000;
12 |
13 | function createDatastore() {
14 | const db = new Datastore(); // in-memory
15 |
16 | return new Promise((resolve, reject) => {
17 | db.loadDatabase(err => {
18 | if (err) return reject(err);
19 | resolve(db);
20 | });
21 | });
22 | }
23 |
24 | function insertFakeEntities(db, count) {
25 | const entities = fakerator.times(fakerator.entity.user, count);
26 | return new Promise((resolve, reject) => {
27 | db.insert(entities, (err, docs) => {
28 | if (err) return reject(err);
29 | resolve(docs);
30 | });
31 | /*}).then(docs => {
32 | return new Promise((resolve, reject) => {
33 | db.count({}, (err, count) => {
34 | if (err) return reject(err);
35 | resolve(docs);
36 | console.log("Number of entities:", count);
37 | });
38 | });*/
39 | });
40 | }
41 |
42 | const bench1 = benchmark.createSuite(`NeDB: Insert (${COUNT})`);
43 | (function (bench) {
44 | const entity1 = {
45 | firstName: "John",
46 | lastName: "Doe",
47 | username: "john.doe81",
48 | email: "john.doe@moleculer.services",
49 | password: "pass1234",
50 | status: 1
51 | };
52 | let db;
53 |
54 | bench.setup(async () => {
55 | db = await createDatastore();
56 | });
57 |
58 | bench.add("db.insert", done => {
59 | db.insert(entity1, done);
60 | });
61 | })(bench1);
62 |
63 | const bench2 = benchmark.createSuite(`NeDB: Entity listing (${COUNT})`);
64 | (function (bench) {
65 | let docs;
66 | let db;
67 |
68 | bench.setup(async () => {
69 | db = await createDatastore();
70 | docs = await insertFakeEntities(db, COUNT);
71 | });
72 |
73 | bench.ref("db.find", done => {
74 | const offset = Math.floor(Math.random() * 80);
75 | db.find().limit(20).skip(offset).exec(done);
76 | });
77 | })(bench2);
78 |
79 | const bench3 = benchmark.createSuite(`NeDB: Entity counting (${COUNT})`);
80 | (function (bench) {
81 | let docs;
82 | let db;
83 |
84 | bench.setup(async () => {
85 | db = await createDatastore();
86 | docs = await insertFakeEntities(db, COUNT);
87 | });
88 |
89 | bench.ref("db.count", done => {
90 | db.count({}, done);
91 | });
92 | })(bench3);
93 |
94 | const bench4 = benchmark.createSuite(`NeDB: Entity getting (${COUNT})`);
95 | (function (bench) {
96 | let docs;
97 | let db;
98 |
99 | bench.setup(async () => {
100 | db = await createDatastore();
101 | docs = await insertFakeEntities(db, COUNT);
102 | });
103 |
104 | bench.add("db.find", done => {
105 | const entity = docs[Math.floor(Math.random() * docs.length)];
106 | db.find({ _id: entity.id }).exec(done);
107 | });
108 | })(bench4);
109 |
110 | const bench5 = benchmark.createSuite(`NeDB: Entity updating (${COUNT})`);
111 | (function (bench) {
112 | let docs;
113 | let db;
114 |
115 | bench.setup(async () => {
116 | db = await createDatastore();
117 | docs = await insertFakeEntities(db, COUNT);
118 | });
119 |
120 | bench.ref("db.update", done => {
121 | const entity = docs[Math.floor(Math.random() * docs.length)];
122 | const newStatus = Math.round(Math.random());
123 | db.update(
124 | { _id: entity._id },
125 | {
126 | $set: {
127 | status: newStatus
128 | }
129 | },
130 | { returnUpdatedDocs: true },
131 | done
132 | );
133 | });
134 | })(bench5);
135 |
136 | const bench6 = benchmark.createSuite(`NeDB: Entity replacing (${COUNT})`);
137 | (function (bench) {
138 | let docs;
139 | let db;
140 |
141 | bench.setup(async () => {
142 | db = await createDatastore();
143 | docs = await insertFakeEntities(db, COUNT);
144 | });
145 |
146 | bench.ref("db.update", done => {
147 | const entity = docs[Math.floor(Math.random() * docs.length)];
148 | entity.status = Math.round(Math.random());
149 | db.update({ _id: entity._id }, entity, { returnUpdatedDocs: true }, done);
150 | });
151 | })(bench6);
152 |
153 | const bench7 = benchmark.createSuite(`NeDB: Entity deleting (${COUNT})`);
154 | (function (bench) {
155 | let docs;
156 | let db;
157 |
158 | bench.setup(async () => {
159 | db = await createDatastore();
160 | docs = await insertFakeEntities(db, COUNT);
161 | });
162 |
163 | bench.ref("db.remove", done => {
164 | const entity = docs[Math.floor(Math.random() * docs.length)];
165 | db.remove({ _id: entity._id }, done);
166 | });
167 | })(bench7);
168 |
169 | benchmark.run([bench1, bench2, bench3, bench4, bench5, bench6, bench7]);
170 |
171 | /* RESULT
172 |
173 | ==================
174 | NeDB benchmark
175 | ==================
176 |
177 | Platform info:
178 | ==============
179 | Windows_NT 10.0.19041 x64
180 | Node.JS: 12.14.1
181 | V8: 7.7.299.13-node.16
182 | Intel(R) Core(TM) i7-4770K CPU @ 3.50GHz × 8
183 |
184 | Suite: NeDB: Insert (1000)
185 | √ db.insert* 52,813 rps
186 |
187 | db.insert* 0% (52,813 rps) (avg: 18μs)
188 | -----------------------------------------------------------------------
189 |
190 | Suite: NeDB: Entity listing (1000)
191 | √ db.find* 7,791 rps
192 |
193 | db.find* (#) 0% (7,791 rps) (avg: 128μs)
194 | -----------------------------------------------------------------------
195 |
196 | Suite: NeDB: Entity counting (1000)
197 | √ db.count* 6,684 rps
198 |
199 | db.count* (#) 0% (6,684 rps) (avg: 149μs)
200 | -----------------------------------------------------------------------
201 |
202 | Suite: NeDB: Entity getting (1000)
203 | √ db.find* 2,838 rps
204 |
205 | db.find* 0% (2,838 rps) (avg: 352μs)
206 | -----------------------------------------------------------------------
207 |
208 | Suite: NeDB: Entity updating (1000)
209 | √ db.update* 35,457 rps
210 |
211 | db.update* (#) 0% (35,457 rps) (avg: 28μs)
212 | -----------------------------------------------------------------------
213 |
214 | Suite: NeDB: Entity replacing (1000)
215 | √ db.update* 34,325 rps
216 |
217 | db.update* (#) 0% (34,325 rps) (avg: 29μs)
218 | -----------------------------------------------------------------------
219 |
220 | Suite: NeDB: Entity deleting (1000)
221 | √ db.remove* 128,375 rps
222 |
223 | db.remove* (#) 0% (128,375 rps) (avg: 7μs)
224 | -----------------------------------------------------------------------
225 |
226 |
227 | */
228 |
--------------------------------------------------------------------------------
/benchmark/suites/transform.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const { ServiceBroker, Context } = require("moleculer");
4 | const DbService = require("../..").Service;
5 | const { writeResult } = require("../utils");
6 | const { generateMarkdown } = require("../generate-result");
7 |
8 | const Fakerator = require("fakerator");
9 | const fakerator = new Fakerator();
10 |
11 | const Benchmarkify = require("benchmarkify");
12 | const benchmark = new Benchmarkify("Moleculer Database benchmark - Transformation benchmark", {
13 | description:
14 | "This is a transformation benchmark. It tests all service methods with and without transformation."
15 | }).printHeader();
16 |
17 | const COUNT = 1000;
18 | const SUITE_NAME = "transform";
19 |
20 | const suites = [];
21 |
22 | const UserServiceSchema = {
23 | name: "users",
24 | mixins: [
25 | DbService({
26 | adapter: { type: "NeDB" }
27 | })
28 | ],
29 | settings: {
30 | fields: {
31 | id: { type: "string", primaryKey: true, columnName: "_id" },
32 | firstName: { type: "string" },
33 | lastName: { type: "string" },
34 | fullName: {
35 | type: "string",
36 | get: ({ entity }) => entity.firstName + " " + entity.lastName
37 | },
38 | username: { type: "string" },
39 | email: { type: "string" },
40 | password: { type: "string", hidden: true },
41 | status: { type: "number", default: 1 }
42 | }
43 | }
44 | };
45 |
46 | const bench1 = benchmark.createSuite("Entity creation");
47 | (function (bench) {
48 | const broker = new ServiceBroker({ logger: false });
49 | const svc = broker.createService(UserServiceSchema);
50 | const ctx = Context.create(broker, null, {});
51 |
52 | const entity1 = {
53 | firstName: "John",
54 | lastName: "Doe",
55 | username: "john.doe81",
56 | email: "john.doe@moleculer.services",
57 | password: "pass1234",
58 | status: 1
59 | };
60 |
61 | bench.setup(() => broker.start());
62 | bench.tearDown(() => broker.stop());
63 |
64 | bench.ref("Without transform", done =>
65 | svc.createEntity(ctx, entity1, { transform: false }).then(done)
66 | );
67 | bench.add("With transform", done => svc.createEntity(ctx, entity1).then(done));
68 | })(bench1);
69 | suites.push(bench1);
70 |
71 | const bench2 = benchmark.createSuite("Entity listing");
72 | (function (bench) {
73 | const broker = new ServiceBroker({ logger: false });
74 | const svc = broker.createService(UserServiceSchema);
75 |
76 | let docs;
77 |
78 | bench.setup(async () => {
79 | await broker.start();
80 |
81 | await svc.clearEntities(ctx);
82 | docs = await svc.createEntities(ctx, fakerator.times(fakerator.entity.user, COUNT), {
83 | returnEntities: true
84 | });
85 | });
86 | bench.tearDown(() => broker.stop());
87 |
88 | const ctx = Context.create(broker, null, {});
89 |
90 | bench.ref("Without transform", done => {
91 | const offset = Math.floor(Math.random() * 80);
92 | return svc.findEntities(ctx, { offset, limit: 20 }, { transform: false }).then(done);
93 | });
94 |
95 | bench.add("With transform", done => {
96 | const offset = Math.floor(Math.random() * 80);
97 | return svc.findEntities(ctx, { offset, limit: 20 }).then(done);
98 | });
99 | })(bench2);
100 | suites.push(bench2);
101 |
102 | const bench3 = benchmark.createSuite("Entity counting");
103 | (function (bench) {
104 | const broker = new ServiceBroker({ logger: false });
105 | const svc = broker.createService(UserServiceSchema);
106 |
107 | let docs;
108 |
109 | bench.setup(async () => {
110 | await broker.start();
111 |
112 | await svc.clearEntities(ctx);
113 | docs = await svc.createEntities(ctx, fakerator.times(fakerator.entity.user, COUNT), {
114 | returnEntities: true
115 | });
116 | });
117 | bench.tearDown(() => broker.stop());
118 |
119 | const ctx = Context.create(broker, null, {});
120 |
121 | bench.ref("Without transform", done => {
122 | return svc.countEntities(ctx).then(done);
123 | });
124 |
125 | /*bench.add("Direct adapter access", done => {
126 | return svc.adapter.collection.countDocuments().then(done);
127 | });*/
128 | })(bench3);
129 | suites.push(bench3);
130 |
131 | const bench4 = benchmark.createSuite("Entity getting");
132 | (function (bench) {
133 | const broker = new ServiceBroker({ logger: false });
134 | const svc = broker.createService(UserServiceSchema);
135 |
136 | let docs;
137 |
138 | bench.setup(async () => {
139 | await broker.start();
140 |
141 | await svc.clearEntities(ctx);
142 | docs = await svc.createEntities(ctx, fakerator.times(fakerator.entity.user, COUNT), {
143 | returnEntities: true
144 | });
145 | });
146 | bench.tearDown(() => broker.stop());
147 |
148 | const ctx = Context.create(broker, null, {});
149 |
150 | bench.ref("Without transform", done => {
151 | const entity = docs[Math.floor(Math.random() * docs.length)];
152 | return svc.resolveEntities(ctx, { id: entity.id }, { transform: false }).then(done);
153 | });
154 |
155 | bench.add("With transform", done => {
156 | const entity = docs[Math.floor(Math.random() * docs.length)];
157 | return svc.resolveEntities(ctx, { id: entity.id }, { transform: true }).then(done);
158 | });
159 |
160 | bench.add("Direct adapter access", done => {
161 | const entity = docs[Math.floor(Math.random() * docs.length)];
162 | return svc
163 | .getAdapter(ctx)
164 | .then(adapter => adapter.findById(entity.id))
165 | .then(done);
166 | });
167 | })(bench4);
168 | suites.push(bench4);
169 |
170 | const bench5 = benchmark.createSuite("Entity updating");
171 | (function (bench) {
172 | const broker = new ServiceBroker({ logger: false });
173 | const svc = broker.createService(UserServiceSchema);
174 |
175 | let docs;
176 |
177 | bench.setup(async () => {
178 | await broker.start();
179 |
180 | await svc.clearEntities(ctx);
181 | docs = await svc.createEntities(ctx, fakerator.times(fakerator.entity.user, COUNT), {
182 | returnEntities: true
183 | });
184 | });
185 | bench.tearDown(() => broker.stop());
186 |
187 | const ctx = Context.create(broker, null, {});
188 |
189 | bench.ref("Without transform", done => {
190 | const entity = docs[Math.floor(Math.random() * docs.length)];
191 | const newStatus = Math.round(Math.random());
192 | return svc
193 | .updateEntity(ctx, { id: entity.id, status: newStatus }, { transform: false })
194 | .then(done);
195 | });
196 |
197 | bench.add("With transform", done => {
198 | const entity = docs[Math.floor(Math.random() * docs.length)];
199 | const newStatus = Math.round(Math.random());
200 | return svc
201 | .updateEntity(ctx, { id: entity.id, status: newStatus }, { transform: true })
202 | .then(done);
203 | });
204 | })(bench5);
205 | suites.push(bench5);
206 |
207 | const bench6 = benchmark.createSuite("Entity replacing");
208 | (function (bench) {
209 | const broker = new ServiceBroker({ logger: false });
210 | const svc = broker.createService(UserServiceSchema);
211 |
212 | let docs;
213 |
214 | bench.setup(async () => {
215 | await broker.start();
216 |
217 | await svc.clearEntities(ctx);
218 | docs = await svc.createEntities(ctx, fakerator.times(fakerator.entity.user, COUNT), {
219 | returnEntities: true
220 | });
221 | });
222 | bench.tearDown(() => broker.stop());
223 |
224 | const ctx = Context.create(broker, null, {});
225 |
226 | bench.ref("Without transform", done => {
227 | const entity = docs[Math.floor(Math.random() * docs.length)];
228 | entity.status = Math.round(Math.random());
229 | return svc.replaceEntity(ctx, entity, { transform: false }).then(done);
230 | });
231 |
232 | bench.add("With transform", done => {
233 | const entity = docs[Math.floor(Math.random() * docs.length)];
234 | entity.status = Math.round(Math.random());
235 | return svc.replaceEntity(ctx, entity, { transform: true }).then(done);
236 | });
237 | })(bench6);
238 | suites.push(bench6);
239 |
240 | const bench7 = benchmark.createSuite("Entity deleting");
241 | (function (bench) {
242 | const broker = new ServiceBroker({ logger: false });
243 | const svc = broker.createService(UserServiceSchema);
244 |
245 | let docs;
246 |
247 | bench.setup(async () => {
248 | await broker.start();
249 |
250 | await svc.clearEntities(ctx);
251 | docs = await svc.createEntities(ctx, fakerator.times(fakerator.entity.user, COUNT), {
252 | returnEntities: true
253 | });
254 | });
255 | bench.tearDown(async () => {
256 | console.log("Remaining record", await svc.countEntities(ctx));
257 | await broker.stop();
258 | });
259 |
260 | const ctx = Context.create(broker, null, {});
261 |
262 | bench.ref("Without transform", done => {
263 | const entity = docs[Math.floor(Math.random() * docs.length)];
264 | return svc
265 | .removeEntity(ctx, { id: entity.id }, { transform: false })
266 | .catch(done)
267 | .then(done);
268 | });
269 |
270 | bench.add("With transform", done => {
271 | const entity = docs[Math.floor(Math.random() * docs.length)];
272 | return svc.removeEntity(ctx, { id: entity.id }, { transform: true }).catch(done).then(done);
273 | });
274 | })(bench7);
275 | suites.push(bench7);
276 |
277 | async function run() {
278 | console.log("Running suites...");
279 | const results = await benchmark.run(suites);
280 | console.log("Save the results to file...");
281 | writeResult(SUITE_NAME, "benchmark_results.json", results);
282 | console.log("Generate README.md...");
283 | await generateMarkdown(SUITE_NAME);
284 | console.log("Done.");
285 | }
286 |
287 | run();
288 |
289 | /* RESULT
290 |
291 | =================================================
292 | Moleculer Database - Transformation benchmark
293 | =================================================
294 |
295 | Platform info:
296 | ==============
297 | Windows_NT 10.0.19041 x64
298 | Node.JS: 12.14.1
299 | V8: 7.7.299.13-node.16
300 | Intel(R) Core(TM) i7-4770K CPU @ 3.50GHz × 8
301 |
302 | Suite: Entity creation (1000)
303 | √ Without transform* 32,042 rps
304 | √ With transform* 24,336 rps
305 |
306 | Without transform* (#) 0% (32,042 rps) (avg: 31μs)
307 | With transform* -24.05% (24,336 rps) (avg: 41μs)
308 | -----------------------------------------------------------------------
309 |
310 | Suite: Entity listing (1000)
311 | √ Without transform* 8,736 rps
312 | √ With transform* 6,508 rps
313 |
314 | Without transform* (#) 0% (8,736 rps) (avg: 114μs)
315 | With transform* -25.51% (6,508 rps) (avg: 153μs)
316 | -----------------------------------------------------------------------
317 |
318 | Suite: Entity counting (1000)
319 | √ Without transform* 1,708 rps
320 |
321 | Without transform* (#) 0% (1,708 rps) (avg: 585μs)
322 | -----------------------------------------------------------------------
323 |
324 | Suite: Entity getting (1000)
325 | √ Without transform* 86,826 rps
326 | √ With transform* 52,076 rps
327 | √ Direct adapter access* 91,199 rps
328 |
329 | Without transform* (#) 0% (86,826 rps) (avg: 11μs)
330 | With transform* -40.02% (52,076 rps) (avg: 19μs)
331 | Direct adapter access* +5.04% (91,199 rps) (avg: 10μs)
332 | -----------------------------------------------------------------------
333 |
334 | Suite: Entity updating (1000)
335 | √ Without transform* 20,704 rps
336 | √ With transform* 18,048 rps
337 |
338 | Without transform* (#) 0% (20,704 rps) (avg: 48μs)
339 | With transform* -12.83% (18,048 rps) (avg: 55μs)
340 | -----------------------------------------------------------------------
341 |
342 | Suite: Entity replacing (1000)
343 | √ Without transform* 19,130 rps
344 | √ With transform* 16,604 rps
345 |
346 | Without transform* (#) 0% (19,130 rps) (avg: 52μs)
347 | With transform* -13.21% (16,604 rps) (avg: 60μs)
348 | -----------------------------------------------------------------------
349 |
350 | Suite: Entity deleting (1000)
351 | √ Without transform* 19,787 rps
352 | √ With transform* 5,769 rps
353 | Remaining record 0
354 |
355 | Without transform* (#) 0% (21,151 rps) (avg: 47μs)
356 | With transform* -62.06% (8,025 rps) (avg: 124μs)
357 | -----------------------------------------------------------------------
358 |
359 | */
360 |
--------------------------------------------------------------------------------
/benchmark/utils.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const { makeDirs } = require("moleculer").Utils;
4 | const path = require("path");
5 | const fs = require("fs");
6 | const qs = require("qs");
7 |
8 | module.exports = {
9 | slug(text) {
10 | return text
11 | .toString()
12 | .toLowerCase()
13 | .replace(/\s+/g, "-") // Replace spaces with -
14 | .replace(/[^\w\-]+/g, "") // Remove all non-word chars
15 | .replace(/\-\-+/g, "-") // Replace multiple - with single -
16 | .replace(/^-+/, "") // Trim - from start of text
17 | .replace(/-+$/, ""); // Trim - from end of text
18 | },
19 |
20 | writeResult(folderName, filename, content) {
21 | const folder = path.join(__dirname, "results", folderName);
22 | makeDirs(folder);
23 |
24 | fs.writeFileSync(path.join(folder, filename), JSON.stringify(content, null, 2), "utf8");
25 | },
26 |
27 | saveMarkdown(filename, rows) {
28 | const content = rows.join("\n");
29 | fs.writeFileSync(filename, content, "utf8");
30 | },
31 |
32 | createChartURL(opts) {
33 | return `https://image-charts.com/chart?${qs.stringify(opts)}`;
34 | },
35 |
36 | numToStr(num, digits = 2) {
37 | return new Intl.NumberFormat("en-US", { maximumFractionDigits: digits }).format(num);
38 | },
39 |
40 | makeTableRow(cells) {
41 | return "| " + cells.join(" | ") + " |";
42 | }
43 | };
44 |
--------------------------------------------------------------------------------
/docs/adapters/Knex.md:
--------------------------------------------------------------------------------
1 | # Knex adapter
2 | This adapter gives access to SQL database engines using the [Knex.js](https://knexjs.org/) library.
3 |
4 | > Knex.js (pronounced /kəˈnɛks/) is a "batteries included" SQL query builder for Postgres, MSSQL, MySQL, MariaDB, SQLite3, Oracle, and Amazon Redshift designed to be flexible, portable, and fun to use.
5 |
6 | ## Install
7 | This module contains the source code of the adapter. You just need to install the dependent library.
8 |
9 | ```bash
10 | npm install knex@^1.0.1
11 | ```
12 |
13 | ## Usage
14 |
15 | ### Connect to SQLite memory database
16 | > To connect SQLite database, you should install the `@vscode/sqlite3` module with `npm install @vscode/sqlite3` command (or `better-sqlite3`).
17 |
18 | ```js
19 | // posts.service.js
20 | const DbService = require("@moleculer/database").Service;
21 |
22 | module.exports = {
23 | name: "posts",
24 | mixins: [DbService({
25 | adapter: {
26 | type: "Knex",
27 | options: {
28 | knex: {
29 | client: "sqlite3",
30 | connection: {
31 | filename: ":memory:"
32 | },
33 | useNullAsDefault: true
34 | }
35 | }
36 | }
37 | })]
38 | }
39 | ```
40 |
41 | ### Connect to PostgreSQL database
42 | > To connect PostgreSQL database you should install the `pg` module with `npm install pg` command.
43 |
44 | ```js
45 | // posts.service.js
46 | const DbService = require("@moleculer/database").Service;
47 |
48 | module.exports = {
49 | name: "posts",
50 | mixins: [DbService({
51 | adapter: {
52 | type: "Knex",
53 | options: {
54 | knex: {
55 | client: "pg",
56 | connection: "postgres://postgres@localhost:5432/moleculer"
57 | }
58 | }
59 | }
60 | })]
61 | }
62 | ```
63 |
64 | ### Connect to MySQL database
65 | > To connect MySQL database you should install the `mysql` module with `npm install mysql` command.
66 |
67 | ```js
68 | // posts.service.js
69 | const DbService = require("@moleculer/database").Service;
70 |
71 | module.exports = {
72 | name: "posts",
73 | mixins: [DbService({
74 | adapter: {
75 | type: "Knex",
76 | options: {
77 | knex: {
78 | client: "pg",
79 | connection: {
80 | host: "127.0.0.1",
81 | user: "root",
82 | password: "pass1234",
83 | database: "moleculer"
84 | }
85 | }
86 | }
87 | }
88 | })]
89 | }
90 | ```
91 |
92 | ### Connect to MSSQL database
93 | > To connect MSSQL database you should install the `tedious` module with `npm install tedious` command.
94 |
95 | ```js
96 | // posts.service.js
97 | const DbService = require("@moleculer/database").Service;
98 |
99 | module.exports = {
100 | name: "posts",
101 | mixins: [DbService({
102 | adapter: {
103 | type: "Knex",
104 | options: {
105 | knex: {
106 | client: "mssql",
107 | connection: {
108 | host: "127.0.0.1",
109 | port: 1433,
110 | user: "sa",
111 | password: "Moleculer@Pass1234",
112 | database: "moleculer",
113 | encrypt: false
114 | }
115 | }
116 | }
117 | }
118 | })]
119 | }
120 | ```
121 |
122 |
123 | ## Options
124 | | Property | Type | Default | Description |
125 | | -------- | ---- | ------- | ----------- |
126 | | `knex` | `Object` | `null` | Available options: http://knexjs.org/#Installation-client |
127 | | `tableName` | `String` | `null` | Table name. If empty, use the service name. |
128 |
129 | ## Raw update
130 | If you want to update an entity and using raw changes, use the [`updateEntity`](../README.md#updateentity) method with `{ raw: true }` options. In this case, you can use the MongoDB-like `$set` and `$inc` operators in the `params` parameter.
131 |
132 | ### Example
133 | ```js
134 | const row = await this.updateEntity(ctx, {
135 | id: "YVdnh5oQCyEIRja0",
136 |
137 | $set: {
138 | status: false,
139 | height: 192
140 | },
141 | $inc: {
142 | age: 1
143 | }
144 | }, { raw: true });
145 | ```
146 |
147 | ## Additional methods
148 |
149 | ### `createTable`
150 | `createTable(fields?: Array