├── .editorconfig ├── .github ├── contributing.md ├── issue_template.md ├── pull_request_template.md ├── stale.yml └── workflows │ ├── nodejs.yml │ └── update-dependencies.yml ├── .gitignore ├── .nycrc ├── .vscode └── launch.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── lib ├── error-handler.js ├── hooks.js ├── index.js ├── service.js └── transaction-manager.js ├── package-lock.json ├── package.json ├── test ├── error-handler.test.js ├── hooks.test.js ├── index.test.js ├── models │ ├── candidate.js │ ├── customer.js │ ├── index.js │ ├── peeps-customid.js │ ├── peeps.js │ ├── pet.js │ ├── post.js │ ├── text-post.js │ ├── todo.js │ ├── token.js │ └── user.js └── transaction-manager.test.js └── types ├── index.d.ts ├── index.test.ts ├── tsconfig.json └── tslint.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.github/contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing to Feathers 2 | 3 | Thank you for contributing to Feathers! :heart: :tada: 4 | 5 | This repo is the main core and where most issues are reported. Feathers embraces modularity and is broken up across many repos. To make this easier to manage we currently use [Zenhub](https://www.zenhub.com/) for issue triage and visibility. They have a free browser plugin you can install so that you can see what is in flight at any time, but of course you also always see current issues in Github. 6 | 7 | ## Report a bug 8 | 9 | Before creating an issue please make sure you have checked out the docs, specifically the [FAQ](https://docs.feathersjs.com/help/faq.html) section. You might want to also try searching Github. It's pretty likely someone has already asked a similar question. 10 | 11 | If you haven't found your answer please feel free to join our [slack channel](http://slack.feathersjs.com), create an issue on Github, or post on [Stackoverflow](http://stackoverflow.com) using the `feathers` or `feathersjs` tag. We try our best to monitor Stackoverflow but you're likely to get more immediate responses in Slack and Github. 12 | 13 | Issues can be reported in the [issue tracker](https://github.com/feathersjs/feathers/issues). Since feathers combines many modules it can be hard for us to assess the root cause without knowing which modules are being used and what your configuration looks like, so **it helps us immensely if you can link to a simple example that reproduces your issue**. 14 | 15 | ## Report a Security Concern 16 | 17 | We take security very seriously at Feathers. We welcome any peer review of our 100% open source code to ensure nobody's Feathers app is ever compromised or hacked. As a web application developer you are responsible for any security breaches. We do our very best to make sure Feathers is as secure as possible by default. 18 | 19 | In order to give the community time to respond and upgrade we strongly urge you report all security issues to us. Send one of the core team members a PM in [Slack](http://slack.feathersjs.com) or email us at hello@feathersjs.com with details and we will respond ASAP. 20 | 21 | For full details refer to our [Security docs](https://docs.feathersjs.com/SECURITY.html). 22 | 23 | ## Pull Requests 24 | 25 | We :heart: pull requests and we're continually working to make it as easy as possible for people to contribute, including a [Plugin Generator](https://github.com/feathersjs/generator-feathers-plugin) and a [common test suite](https://github.com/feathersjs/feathers-service-tests) for database adapters. 26 | 27 | We prefer small pull requests with minimal code changes. The smaller they are the easier they are to review and merge. A core team member will pick up your PR and review it as soon as they can. They may ask for changes or reject your pull request. This is not a reflection of you as an engineer or a person. Please accept feedback graciously as we will also try to be sensitive when providing it. 28 | 29 | Although we generally accept many PRs they can be rejected for many reasons. We will be as transparent as possible but it may simply be that you do not have the same context or information regarding the roadmap that the core team members have. We value the time you take to put together any contributions so we pledge to always be respectful of that time and will try to be as open as possible so that you don't waste it. :smile: 30 | 31 | **All PRs (except documentation) should be accompanied with tests and pass the linting rules.** 32 | 33 | ### Code style 34 | 35 | Before running the tests from the `test/` folder `npm test` will run ESlint. You can check your code changes individually by running `npm run lint`. 36 | 37 | ### ES6 compilation 38 | 39 | Feathers uses [Babel](https://babeljs.io/) to leverage the latest developments of the JavaScript language. All code and samples are currently written in ES2015. To transpile the code in this repository run 40 | 41 | > npm run compile 42 | 43 | __Note:__ `npm test` will run the compilation automatically before the tests. 44 | 45 | ### Tests 46 | 47 | [Mocha](http://mochajs.org/) tests are located in the `test/` folder and can be run using the `npm run mocha` or `npm test` (with ESLint and code coverage) command. 48 | 49 | ### Documentation 50 | 51 | Feathers documentation is contained in Markdown files in the [feathers-docs](https://github.com/feathersjs/feathers-docs) repository. To change the documentation submit a pull request to that repo, referencing any other PR if applicable, and the docs will be updated with the next release. 52 | 53 | ## External Modules 54 | 55 | If you're written something awesome for Feathers, the Feathers ecosystem, or using Feathers please add it to the [showcase](https://docs.feathersjs.com/why/showcase.html). You also might want to check out the [Plugin Generator](https://github.com/feathersjs/generator-feathers-plugin) that can be used to scaffold plugins to be Feathers compliant from the start. 56 | 57 | If you think it would be a good core module then please contact one of the Feathers core team members in [Slack](http://slack.feathersjs.com) and we can discuss whether it belongs in core and how to get it there. :beers: 58 | 59 | ## Contributor Code of Conduct 60 | 61 | As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. 62 | 63 | We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion. 64 | 65 | Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct. 66 | 67 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team. 68 | 69 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. 70 | 71 | This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/) 72 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | ### Steps to reproduce 2 | 3 | (First please check that this issue is not already solved as [described 4 | here](https://github.com/feathersjs/feathers/blob/master/.github/contributing.md#report-a-bug)) 5 | 6 | - [ ] Tell us what broke. The more detailed the better. 7 | - [ ] If you can, please create a simple example that reproduces the issue and link to a gist, jsbin, repo, etc. 8 | 9 | ### Expected behavior 10 | Tell us what should happen 11 | 12 | ### Actual behavior 13 | Tell us what happens instead 14 | 15 | ### System configuration 16 | 17 | Tell us about the applicable parts of your setup. 18 | 19 | **Module versions** (especially the part that's not working): 20 | 21 | **NodeJS version**: 22 | 23 | **Operating System**: 24 | 25 | **Browser Version**: 26 | 27 | **React Native Version**: 28 | 29 | **Module Loader**: -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### Summary 2 | 3 | (If you have not already please refer to the contributing guideline as [described 4 | here](https://github.com/feathersjs/feathers/blob/master/.github/contributing.md#pull-requests)) 5 | 6 | - [ ] Tell us about the problem your pull request is solving. 7 | - [ ] Are there any open issues that are related to this? 8 | - [ ] Is this PR dependent on PRs in other repos? 9 | 10 | If so, please mention them to keep the conversations linked together. 11 | 12 | ### Other Information 13 | 14 | If there's anything else that's important and relevant to your pull 15 | request, mention that information here. This could include 16 | benchmarks, or other information. 17 | 18 | Your PR will be reviewed by a core team member and they will work with you to get your changes merged in a timely manner. If merged your PR will automatically be added to the changelog in the next release. 19 | 20 | If your changes involve documentation updates please mention that and link the appropriate PR in [feathers-docs](https://github.com/feathersjs/feathers-docs). 21 | 22 | Thanks for contributing to Feathers! :heart: -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 84 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - greenkeeper 8 | - bug 9 | - security 10 | - enhancement 11 | # Label to use when marking an issue as stale 12 | staleLabel: wontfix 13 | # Comment to post when marking an issue as stale. Set to `false` to disable 14 | markComment: > 15 | This issue has been automatically marked as stale because it has not had 16 | recent activity. It will be closed if no further activity occurs. 17 | Apologies if the issue could not be resolved. FeathersJS ecosystem 18 | modules are community maintained so there may be a chance that there isn't anybody 19 | available to address the issue at the moment. 20 | For other ways to get help [see here](https://docs.feathersjs.com/help/readme.html). 21 | # Comment to post when closing a stale issue. Set to `false` to disable 22 | closeComment: false 23 | # Only close stale issues 24 | only: issues 25 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | strategy: 11 | matrix: 12 | node-version: [12.x, 16.x] 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Start MongoDB 17 | uses: supercharge/mongodb-github-action@1.6.0 18 | with: 19 | mongodb-version: 4.4 20 | mongodb-replica-set: test-rs 21 | - name: Use Node.js ${{ matrix.node-version }} 22 | uses: actions/setup-node@v1 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | - run: npm install 26 | - run: npm test 27 | env: 28 | CI: true 29 | -------------------------------------------------------------------------------- /.github/workflows/update-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@v2 12 | - name: Use Node.js 13 | uses: actions/setup-node@v1 14 | with: 15 | node-version: '15.x' 16 | - run: npm ci 17 | - run: | 18 | git config user.name "GitHub Actions Bot" 19 | git config user.email "hello@feathersjs.com" 20 | git checkout -b update-dependencies-$GITHUB_RUN_ID 21 | - run: | 22 | npm run update-dependencies 23 | npm install 24 | - run: | 25 | git commit -am "chore(dependencies): Update dependencies" 26 | git push origin update-dependencies-$GITHUB_RUN_ID 27 | - run: | 28 | gh pr create --title "chore(dependencies): Update all dependencies" --body "" 29 | env: 30 | GITHUB_TOKEN: ${{secrets.CI_ACCESS_TOKEN}} 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | # Logs 4 | logs 5 | *.log 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 19 | .grunt 20 | 21 | # Compiled binary addons (http://nodejs.org/api/addons.html) 22 | build/Release 23 | 24 | # Dependency directory 25 | # Commenting this out is preferred by some people, see 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 27 | node_modules 28 | 29 | # Users Environment Variables 30 | .lock-wscript 31 | 32 | data/ 33 | -------------------------------------------------------------------------------- /.nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "verbose": false, 3 | "tempDirectory": "./coverage/.tmp", 4 | "semistandard": { 5 | "env": [ 6 | "mocha" 7 | ] 8 | }, 9 | "extension": [ 10 | ".ts", 11 | ".tsx", 12 | ".js" 13 | ], 14 | "exclude": [ 15 | "**/test/*" 16 | ], 17 | "print": "detail", 18 | "reporter": [ 19 | "html", 20 | "text", 21 | "text-summary", 22 | "lcov" 23 | ], 24 | "watermarks": { 25 | "statements": [ 26 | 70, 27 | 90 28 | ], 29 | "lines": [ 30 | 70, 31 | 90 32 | ], 33 | "functions": [ 34 | 70, 35 | 90 36 | ], 37 | "branches": [ 38 | 70, 39 | 90 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible 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": "Mocha Tests", 11 | "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", 12 | "args": [ 13 | "-u", 14 | "bdd", 15 | "--timeout", 16 | "999999", 17 | "--colors", 18 | "${workspaceFolder}/test" 19 | ], 20 | "internalConsoleOptions": "openOnSessionStart", 21 | "skipFiles": [ 22 | "/**" 23 | ] 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [Unreleased](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/HEAD) 4 | 5 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v8.5.1...HEAD) 6 | 7 | **Merged pull requests:** 8 | 9 | - Fixing compile error on Query in TS [\#419](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/419) ([asadoll](https://github.com/asadoll)) 10 | 11 | ## [v8.5.1](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v8.5.1) (2021-11-23) 12 | 13 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v8.5.0...v8.5.1) 14 | 15 | ## [v8.5.0](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v8.5.0) (2021-11-16) 16 | 17 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v8.4.2...v8.5.0) 18 | 19 | **Closed issues:** 20 | 21 | - Support specifying which replica set to read from [\#407](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/407) 22 | 23 | **Merged pull requests:** 24 | 25 | - add query modifier function support. [\#409](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/409) ([mrfrase3](https://github.com/mrfrase3)) 26 | 27 | ## [v8.4.2](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v8.4.2) (2021-11-11) 28 | 29 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v8.4.1...v8.4.2) 30 | 31 | **Closed issues:** 32 | 33 | - type definition error on MongooseServiceOptions\ [\#412](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/412) 34 | 35 | **Merged pull requests:** 36 | 37 | - Fix typing [\#418](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/418) ([bartduisters](https://github.com/bartduisters)) 38 | - Fix typo in README [\#416](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/416) ([ericirish](https://github.com/ericirish)) 39 | - chore\(dependencies\): Update all dependencies [\#415](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/415) ([daffl](https://github.com/daffl)) 40 | 41 | ## [v8.4.1](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v8.4.1) (2021-10-21) 42 | 43 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v8.4.0...v8.4.1) 44 | 45 | **Closed issues:** 46 | 47 | - Fix TSLint errors after upgrade [\#411](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/411) 48 | 49 | **Merged pull requests:** 50 | 51 | - Fix TypeScript linting [\#414](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/414) ([daffl](https://github.com/daffl)) 52 | - Update index.d.ts [\#413](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/413) ([bartduisters](https://github.com/bartduisters)) 53 | 54 | ## [v8.4.0](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v8.4.0) (2021-09-29) 55 | 56 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v8.3.3...v8.4.0) 57 | 58 | **Closed issues:** 59 | 60 | - mongodump doesn't support collections with a slash in their name [\#408](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/408) 61 | - whitelist $and operator problems [\#406](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/406) 62 | - how release some function look intro [\#405](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/405) 63 | - `upsert: true` does not support `multi: 'patch'` [\#404](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/404) 64 | - `upsert: true` skips schema validation [\#403](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/403) 65 | - Mongoose service not returning Map attribute [\#398](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/398) 66 | 67 | **Merged pull requests:** 68 | 69 | - Update to Mongoose 6 and upgrade project infrastructure [\#410](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/410) ([daffl](https://github.com/daffl)) 70 | - fix: create returns undefined if data is empty array \(for multi\) [\#394](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/394) ([bwgjoseph](https://github.com/bwgjoseph)) 71 | - Improved typing of Model property [\#390](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/390) ([rnbrady](https://github.com/rnbrady)) 72 | 73 | ## [v8.3.3](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v8.3.3) (2021-04-08) 74 | 75 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v8.3.2...v8.3.3) 76 | 77 | **Merged pull requests:** 78 | 79 | - Only $populate if it's whitelisted [\#402](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/402) ([GautierT](https://github.com/GautierT)) 80 | 81 | ## [v8.3.2](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v8.3.2) (2021-04-08) 82 | 83 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v8.3.1...v8.3.2) 84 | 85 | **Fixed bugs:** 86 | 87 | - Patch changed queries [\#377](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/377) 88 | 89 | **Closed issues:** 90 | 91 | - Security Issue - whitelist doesn't remove $populate [\#400](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/400) 92 | - Sort outer collection by populated inner collections [\#399](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/399) 93 | - Compatibility issues with mongoose \< 5.2.0 [\#393](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/393) 94 | - Crear vistas en feathers-mongoose [\#392](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/392) 95 | - "command insert requires authentication", db adapter not production-ready? [\#389](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/389) 96 | - An in-range update of mongoose is breaking the build 🚨 [\#376](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/376) 97 | 98 | **Merged pull requests:** 99 | 100 | - Only allow $populate when listed [\#401](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/401) ([daffl](https://github.com/daffl)) 101 | 102 | ## [v8.3.1](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v8.3.1) (2020-06-22) 103 | 104 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/8.3.0...v8.3.1) 105 | 106 | **Closed issues:** 107 | 108 | - post working even when not passing in multi array [\#387](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/387) 109 | - Invalid query parameter $elemMatch [\#384](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/384) 110 | - bug in custom route hooks \( authentication \) [\#381](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/381) 111 | - Data has been write to test database [\#380](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/380) 112 | - No record found for id... when authenticating [\#378](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/378) 113 | - Is this compatible with Amazon's DocumentDB? [\#374](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/374) 114 | - Support arrayFilter in patch [\#373](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/373) 115 | - \_get and \_patch "No record found for id" [\#369](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/369) 116 | - Can't get softDelete2 working properly!! [\#367](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/367) 117 | - An in-range update of dtslint is breaking the build 🚨 [\#366](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/366) 118 | - Incorrect behavior in a shard cluster [\#361](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/361) 119 | - An in-range update of @feathersjs/adapter-commons is breaking the build 🚨 [\#360](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/360) 120 | - Typescipt definitions do not include service.setup\(\) method [\#357](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/357) 121 | 122 | **Merged pull requests:** 123 | 124 | - Fix params.transactionOpen reset in rollback [\#386](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/386) ([wcheung26](https://github.com/wcheung26)) 125 | - Update sinon to the latest version 🚀 [\#372](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/372) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) 126 | - Update dtslint to the latest version 🚀 [\#371](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/371) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) 127 | - Update all dependencies and Types version [\#368](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/368) ([daffl](https://github.com/daffl)) 128 | 129 | ## [8.3.0](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/8.3.0) (2020-01-08) 130 | 131 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v8.3.0...8.3.0) 132 | 133 | ## [v8.3.0](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v8.3.0) (2020-01-08) 134 | 135 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v8.2.0...v8.3.0) 136 | 137 | **Fixed bugs:** 138 | 139 | - service.patch leads to 404 if query contains the patched field [\#345](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/345) 140 | - patch is incorrectly applying the entire query when re-fetching [\#321](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/321) 141 | 142 | **Merged pull requests:** 143 | 144 | - update '\_remove' method [\#364](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/364) ([sarkistlt](https://github.com/sarkistlt)) 145 | 146 | ## [v8.2.0](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v8.2.0) (2020-01-05) 147 | 148 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v8.1.1...v8.2.0) 149 | 150 | **Merged pull requests:** 151 | 152 | - Add launch.json for mocha tests & fix typescript error. [\#365](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/365) ([marshallswain](https://github.com/marshallswain)) 153 | - Update sinon to the latest version 🚀 [\#363](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/363) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) 154 | - :bug: fix 404 on patch call [\#362](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/362) ([arfanliaqat](https://github.com/arfanliaqat)) 155 | 156 | ## [v8.1.1](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v8.1.1) (2019-12-06) 157 | 158 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v8.1.0...v8.1.1) 159 | 160 | **Closed issues:** 161 | 162 | - feathers service [\#352](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/352) 163 | - An in-range update of @feathersjs/adapter-commons is breaking the build 🚨 [\#350](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/350) 164 | 165 | **Merged pull requests:** 166 | 167 | - Update all dependencies [\#359](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/359) ([daffl](https://github.com/daffl)) 168 | - fix: handling of params.mongoose in transaction [\#358](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/358) ([florianbepunkt](https://github.com/florianbepunkt)) 169 | - Update dtslint to the latest version 🚀 [\#356](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/356) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) 170 | - Update dtslint to version 1.0.2 [\#355](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/355) ([daffl](https://github.com/daffl)) 171 | - Greenkeeper/@feathersjs/adapter commons 4.3.8 [\#351](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/351) ([daffl](https://github.com/daffl)) 172 | 173 | ## [v8.1.0](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v8.1.0) (2019-10-07) 174 | 175 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v8.0.3...v8.1.0) 176 | 177 | **Implemented enhancements:** 178 | 179 | - `whitelist` property should merge with mongoose defaults [\#347](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/347) 180 | 181 | **Merged pull requests:** 182 | 183 | - Update all dependencies [\#349](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/349) ([daffl](https://github.com/daffl)) 184 | - Merge whitelist to always include $and [\#348](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/348) ([daffl](https://github.com/daffl)) 185 | 186 | ## [v8.0.3](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v8.0.3) (2019-09-29) 187 | 188 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v8.0.2...v8.0.3) 189 | 190 | **Closed issues:** 191 | 192 | - An in-range update of run-rs is breaking the build 🚨 [\#341](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/341) 193 | - An in-range update of mongoose is breaking the build 🚨 [\#340](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/340) 194 | - An in-range update of mongoose is breaking the build 🚨 [\#337](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/337) 195 | 196 | **Merged pull requests:** 197 | 198 | - Update run-rs to the latest version 🚀 [\#346](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/346) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) 199 | - Update all dependencies [\#344](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/344) ([daffl](https://github.com/daffl)) 200 | - Greenkeeper/run rs 0.5.5 [\#343](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/343) ([daffl](https://github.com/daffl)) 201 | - Pass entity type to AdapterService\ [\#342](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/342) ([Palivonas](https://github.com/Palivonas)) 202 | - Update semistandard to the latest version 🚀 [\#338](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/338) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) 203 | 204 | ## [v8.0.2](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v8.0.2) (2019-08-07) 205 | 206 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v8.0.1...v8.0.2) 207 | 208 | **Closed issues:** 209 | 210 | - Returned objects not lean. Cannot set lean option. \(Typescript\) [\#335](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/335) 211 | - populate not working for mongoose model [\#328](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/328) 212 | 213 | **Merged pull requests:** 214 | 215 | - Add missing options to definitions [\#336](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/336) ([daffl](https://github.com/daffl)) 216 | 217 | ## [v8.0.1](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v8.0.1) (2019-07-20) 218 | 219 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v8.0.0...v8.0.1) 220 | 221 | **Closed issues:** 222 | 223 | - get query object [\#332](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/332) 224 | 225 | **Merged pull requests:** 226 | 227 | - Add missing types mapping [\#334](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/334) ([daffl](https://github.com/daffl)) 228 | - Update dtslint to the latest version 🚀 [\#333](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/333) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) 229 | 230 | ## [v8.0.0](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v8.0.0) (2019-07-07) 231 | 232 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v7.3.2...v8.0.0) 233 | 234 | **Closed issues:** 235 | 236 | - Change \_Id to id [\#325](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/325) 237 | - How to pass collation from client? [\#322](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/322) 238 | - Populate 2 levels deep [\#320](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/320) 239 | - $populate example in docs has a typo [\#319](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/319) 240 | - Why is $text not whitelisted when querying? [\#318](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/318) 241 | - An in-range update of mongoose is breaking the build 🚨 [\#317](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/317) 242 | - Help with inserting subdocuments [\#316](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/316) 243 | - gridfs support? [\#315](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/315) 244 | - get object through the \_id reference [\#314](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/314) 245 | - Not able to receive the response in patch request when $push is used in feathersjs. [\#313](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/313) 246 | 247 | **Merged pull requests:** 248 | 249 | - Fix query merging on patch [\#330](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/330) ([daffl](https://github.com/daffl)) 250 | - Add TypeScript definitions and upgrade tests to Feathers 4 [\#329](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/329) ([daffl](https://github.com/daffl)) 251 | - Update all dependencies [\#327](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/327) ([daffl](https://github.com/daffl)) 252 | - Update run-rs to the latest version 🚀 [\#312](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/312) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) 253 | 254 | ## [v7.3.2](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v7.3.2) (2019-03-04) 255 | 256 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v7.3.1...v7.3.2) 257 | 258 | **Closed issues:** 259 | 260 | - Invalid query parameter $and [\#309](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/309) 261 | - An in-range update of sinon is breaking the build 🚨 [\#308](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/308) 262 | - An in-range update of mongoose is breaking the build 🚨 [\#307](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/307) 263 | - Cannot read property 'startSession' of undefined [\#304](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/304) 264 | - An in-range update of mocha is breaking the build 🚨 [\#303](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/303) 265 | - An in-range update of mongoose is breaking the build 🚨 [\#301](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/301) 266 | - An in-range update of mongoose is breaking the build 🚨 [\#300](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/300) 267 | 268 | **Merged pull requests:** 269 | 270 | - Whitelist and $and and $regex [\#311](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/311) ([daffl](https://github.com/daffl)) 271 | - Use Mongoose directly to start a session [\#310](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/310) ([daffl](https://github.com/daffl)) 272 | - Greenkeeper/mocha 6.0.2 [\#306](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/306) ([daffl](https://github.com/daffl)) 273 | - Update mocha to the latest version 🚀 [\#302](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/302) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) 274 | - Fix: Added test case for find transaction [\#299](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/299) ([Sharveshkumar](https://github.com/Sharveshkumar)) 275 | 276 | ## [v7.3.1](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v7.3.1) (2019-01-31) 277 | 278 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v7.3.0...v7.3.1) 279 | 280 | **Closed issues:** 281 | 282 | - $and condition is not satisfied. [\#297](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/297) 283 | 284 | **Merged pull requests:** 285 | 286 | - Fix: Transaction for find and get. [\#298](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/298) ([Sharveshkumar](https://github.com/Sharveshkumar)) 287 | 288 | ## [v7.3.0](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v7.3.0) (2019-01-17) 289 | 290 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v7.2.0...v7.3.0) 291 | 292 | **Closed issues:** 293 | 294 | - Conflict error for compound indexes is not correct [\#258](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/258) 295 | 296 | **Merged pull requests:** 297 | 298 | - Expose original MongoDB error following feathers-sequelize methodology [\#296](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/296) ([cdimitroulas](https://github.com/cdimitroulas)) 299 | 300 | ## [v7.2.0](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v7.2.0) (2019-01-13) 301 | 302 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v7.1.3...v7.2.0) 303 | 304 | **Fixed bugs:** 305 | 306 | - unknown top level operator: $populate [\#293](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/293) 307 | 308 | **Closed issues:** 309 | 310 | - $populate as query param for create not working [\#268](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/268) 311 | 312 | **Merged pull requests:** 313 | 314 | - Fix populate usage and enable for .create [\#295](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/295) ([daffl](https://github.com/daffl)) 315 | - Make sure ids in queries are respected [\#294](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/294) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) 316 | 317 | ## [v7.1.3](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v7.1.3) (2019-01-06) 318 | 319 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v7.1.2...v7.1.3) 320 | 321 | **Closed issues:** 322 | 323 | - Failed to patch with null, error: TypeError: data.map is not a function at mapIds [\#289](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/289) 324 | 325 | **Merged pull requests:** 326 | 327 | - Make sure data is never paginated [\#292](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/292) ([daffl](https://github.com/daffl)) 328 | 329 | ## [v7.1.2](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v7.1.2) (2019-01-06) 330 | 331 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v7.1.1...v7.1.2) 332 | 333 | **Closed issues:** 334 | 335 | - Extra data directory included since version 7.0.0 [\#290](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/290) 336 | 337 | **Merged pull requests:** 338 | 339 | - Do not publish data directory [\#291](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/291) ([daffl](https://github.com/daffl)) 340 | 341 | ## [v7.1.1](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v7.1.1) (2018-12-29) 342 | 343 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v7.1.0...v7.1.1) 344 | 345 | **Merged pull requests:** 346 | 347 | - Add default params to hook-less methods [\#288](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/288) ([daffl](https://github.com/daffl)) 348 | 349 | ## [v7.1.0](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v7.1.0) (2018-12-29) 350 | 351 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v7.0.0...v7.1.0) 352 | 353 | **Closed issues:** 354 | 355 | - feathers update service cleans document [\#274](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/274) 356 | 357 | **Merged pull requests:** 358 | 359 | - Added hooks to support mongo-db transaction [\#276](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/276) ([jaiyashree](https://github.com/jaiyashree)) 360 | 361 | ## [v7.0.0](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v7.0.0) (2018-12-18) 362 | 363 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v6.3.0...v7.0.0) 364 | 365 | **Implemented enhancements:** 366 | 367 | - params.query is ignored for returned data when removing a record [\#244](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/244) 368 | 369 | **Security fixes:** 370 | 371 | - $populate security risk [\#207](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/207) 372 | 373 | **Closed issues:** 374 | 375 | - An in-range update of @feathersjs/express is breaking the build 🚨 [\#284](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/284) 376 | - An in-range update of @feathersjs/errors is breaking the build 🚨 [\#283](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/283) 377 | - An in-range update of mongoose is breaking the build 🚨 [\#282](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/282) 378 | - An in-range update of mongoose is breaking the build 🚨 [\#278](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/278) 379 | - how to disable a hook in call a method? [\#277](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/277) 380 | - An in-range update of mongoose is breaking the build 🚨 [\#272](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/272) 381 | 382 | **Merged pull requests:** 383 | 384 | - Upgrade to @feathersjs/adapter-commons and latest common service features [\#286](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/286) ([daffl](https://github.com/daffl)) 385 | - Update run-rs setup [\#285](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/285) ([daffl](https://github.com/daffl)) 386 | - feat: add query filters additionally to id in the get hook [\#280](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/280) ([aliveghost04](https://github.com/aliveghost04)) 387 | - Update README.md [\#279](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/279) ([einstenj](https://github.com/einstenj)) 388 | 389 | ## [v6.3.0](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v6.3.0) (2018-11-27) 390 | 391 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v6.2.0...v6.3.0) 392 | 393 | **Implemented enhancements:** 394 | 395 | - Passing through collation options for case insensitive sorting [\#211](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/211) 396 | 397 | **Closed issues:** 398 | 399 | - Populating multiple paths is not supported [\#271](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/271) 400 | - An in-range update of @feathersjs/socketio is breaking the build 🚨 [\#267](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/267) 401 | - Schema validation does not run for patch requests [\#266](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/266) 402 | - Bug $near on version 6.1.4 not working, 6.1.3 return wrong total \( collection total, not total found \) [\#264](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/264) 403 | 404 | **Merged pull requests:** 405 | 406 | - Fix deprecation warnings and update dependencies [\#275](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/275) ([daffl](https://github.com/daffl)) 407 | - Add missing property option in readme [\#270](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/270) ([aliveghost04](https://github.com/aliveghost04)) 408 | - Update semistandard to the latest version 🚀 [\#269](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/269) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) 409 | - Feat: mongo opt to return writeResult [\#261](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/261) ([54M5M17H](https://github.com/54M5M17H)) 410 | 411 | ## [v6.2.0](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v6.2.0) (2018-10-03) 412 | 413 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v6.1.4...v6.2.0) 414 | 415 | **Fixed bugs:** 416 | 417 | - Inconsistent discriminator API [\#247](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/247) 418 | 419 | **Closed issues:** 420 | 421 | - Error: You must provide a Mongoose Model \(at new Service\) on Model variable name change [\#265](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/265) 422 | - An in-range update of @feathersjs/errors is breaking the build 🚨 [\#262](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/262) 423 | - patch model with a discriminator [\#259](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/259) 424 | - \[question\]\[Documents\] RunValidators is set by default? [\#255](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/255) 425 | - There is total but no data [\#254](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/254) 426 | 427 | **Merged pull requests:** 428 | 429 | - Handled collation param for case insensitive indexes [\#263](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/263) ([superbarne](https://github.com/superbarne)) 430 | - fix test because mongoose broke it [\#260](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/260) ([superbarne](https://github.com/superbarne)) 431 | - Update default settings in docs [\#256](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/256) ([ghost](https://github.com/ghost)) 432 | - reconcile create handler discriminatorKey [\#253](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/253) ([makslevental](https://github.com/makslevental)) 433 | 434 | ## [v6.1.4](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v6.1.4) (2018-08-08) 435 | 436 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v6.1.3...v6.1.4) 437 | 438 | **Closed issues:** 439 | 440 | - DeprecationWarning: collection.count is deprecated, and will be removed in a future version [\#246](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/246) 441 | 442 | **Merged pull requests:** 443 | 444 | - `estimatedDocumentCount` to `countDocuments` [\#252](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/252) ([giovannilobitos](https://github.com/giovannilobitos)) 445 | 446 | ## [v6.1.3](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v6.1.3) (2018-08-06) 447 | 448 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v6.1.2...v6.1.3) 449 | 450 | **Merged pull requests:** 451 | 452 | - Updated to remove the deprecated .count [\#250](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/250) ([Mattchewone](https://github.com/Mattchewone)) 453 | - Update sinon to the latest version 🚀 [\#243](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/243) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) 454 | 455 | ## [v6.1.2](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v6.1.2) (2018-06-03) 456 | 457 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v6.1.1...v6.1.2) 458 | 459 | **Closed issues:** 460 | 461 | - Patch/update should load the document and then save to call defaults/setters [\#231](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/231) 462 | - Working example of sort by distance with latitude/longitude and maxDistance? [\#230](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/230) 463 | - Virtuals Lean and Populate and toObject [\#198](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/198) 464 | 465 | **Merged pull requests:** 466 | 467 | - Update uberproto to the latest version 🚀 [\#242](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/242) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) 468 | 469 | ## [v6.1.1](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v6.1.1) (2018-04-19) 470 | 471 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v6.1.0...v6.1.1) 472 | 473 | **Closed issues:** 474 | 475 | - Mongoose 5 warning "The `useMongoClient` option is no longer necessary in mongoose 5.x, please remove it." [\#239](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/239) 476 | - Grouping [\#236](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/236) 477 | - Mongoose ObjectId not be recognised as \_id in create [\#232](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/232) 478 | 479 | **Merged pull requests:** 480 | 481 | - Remove indexes [\#241](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/241) ([daffl](https://github.com/daffl)) 482 | - Fix bug where regex fails in error-handler [\#240](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/240) ([cdimitroulas](https://github.com/cdimitroulas)) 483 | - Update sinon-chai to the latest version 🚀 [\#234](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/234) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) 484 | 485 | ## [v6.1.0](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v6.1.0) (2018-01-20) 486 | 487 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v6.0.0...v6.1.0) 488 | 489 | **Merged pull requests:** 490 | 491 | - Update mocha to the latest version 🚀 [\#229](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/229) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) 492 | - Update mongoose to the latest version 🚀 [\#228](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/228) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) 493 | - Update semistandard to the latest version 🚀 [\#227](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/227) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) 494 | 495 | ## [v6.0.0](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v6.0.0) (2017-12-03) 496 | 497 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v5.1.2...v6.0.0) 498 | 499 | **Implemented enhancements:** 500 | 501 | - Suggestion - insertMany [\#197](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/197) 502 | 503 | **Closed issues:** 504 | 505 | - mongoose validation error \(in hook\) kills nodejs process [\#222](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/222) 506 | - Add example for nested schemas using feathers [\#221](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/221) 507 | - Are nested schemas saved? [\#220](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/220) 508 | - Feathers error 409 - conflict: message 'follows: null already exists'' [\#219](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/219) 509 | - Find Query with $in array of ObjectId's [\#216](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/216) 510 | - Can not call update with a null id [\#214](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/214) 511 | - poulate after create hook [\#213](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/213) 512 | - Mongoose query aggregation [\#212](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/212) 513 | - Can't update field that is used as id [\#202](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/202) 514 | - $populate enhancement [\#149](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/149) 515 | - Mongoose ValidationError messages do not surface [\#116](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/116) 516 | 517 | **Merged pull requests:** 518 | 519 | - Remove examples [\#225](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/225) ([daffl](https://github.com/daffl)) 520 | - Update to Feathers Buzzard \(v3\) [\#224](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/224) ([daffl](https://github.com/daffl)) 521 | - Update to new plugin infrastructure [\#223](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/223) ([daffl](https://github.com/daffl)) 522 | - Update mocha to the latest version 🚀 [\#217](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/217) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) 523 | - Update sinon to the latest version 🚀 [\#215](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/215) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) 524 | 525 | ## [v5.1.2](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v5.1.2) (2017-08-14) 526 | 527 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v5.1.1...v5.1.2) 528 | 529 | **Closed issues:** 530 | 531 | - Changing embedded documents [\#208](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/208) 532 | - Query $in with more than 21 items throw a MongooseError.CastError [\#205](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/205) 533 | - Service should provide a hook to loaded resource or respect filters in all methods [\#204](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/204) 534 | - Custom ID Not working [\#189](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/189) 535 | - Discriminators is not working [\#188](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/188) 536 | - Make sure create many works with lean:true. Follow up on \#160 [\#161](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/161) 537 | - PUT \(update\) does not run mongoose validators [\#115](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/115) 538 | 539 | **Merged pull requests:** 540 | 541 | - Update sinon to the latest version 🚀 [\#206](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/206) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) 542 | - Fixing broken links to docs [\#203](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/203) ([corymsmith](https://github.com/corymsmith)) 543 | - Ensure bulk create returns plain objects when lean is 'true' [\#186](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/186) ([DesignByOnyx](https://github.com/DesignByOnyx)) 544 | 545 | ## [v5.1.1](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v5.1.1) (2017-07-07) 546 | 547 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v5.1.0...v5.1.1) 548 | 549 | **Closed issues:** 550 | 551 | - Mongoose middleware not working correctly with feathers? [\#196](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/196) 552 | - Mongoose dependency issues [\#193](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/193) 553 | - An in-range update of mongoose is breaking the build 🚨 [\#192](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/192) 554 | - Default for mongoose update not really good \(override true\) [\#191](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/191) 555 | - Using \[$in\] with a single value [\#185](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/185) 556 | 557 | **Merged pull requests:** 558 | 559 | - Handle upserts with patch [\#200](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/200) ([marshallswain](https://github.com/marshallswain)) 560 | - Greenkeeper/mongoose 4.10.4 [\#195](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/195) ([daffl](https://github.com/daffl)) 561 | - Update chai to the latest version 🚀 [\#194](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/194) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) 562 | - Update feathers-socketio to the latest version 🚀 [\#190](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/190) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) 563 | - Update feathers-service-tests to the latest version 🚀 [\#187](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/187) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) 564 | 565 | ## [v5.1.0](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v5.1.0) (2017-04-29) 566 | 567 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v5.0.3...v5.1.0) 568 | 569 | **Closed issues:** 570 | 571 | - An in-range update of mongoose is breaking the build 🚨 [\#184](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/184) 572 | - Can't use mongoose plugin based on pre with patch or put [\#178](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/178) 573 | - lean and populate [\#169](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/169) 574 | 575 | **Merged pull requests:** 576 | 577 | - Update semistandard to the latest version 🚀 [\#183](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/183) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) 578 | - Update feathers-hooks to the latest version 🚀 [\#182](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/182) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) 579 | - Update dependencies to enable Greenkeeper 🌴 [\#181](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/181) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) 580 | - Discriminated Model Support [\#180](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/180) ([thebarndog](https://github.com/thebarndog)) 581 | - Update sinon to version 2.0.0 🚀 [\#176](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/176) ([greenkeeperio-bot](https://github.com/greenkeeperio-bot)) 582 | 583 | ## [v5.0.3](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v5.0.3) (2017-03-02) 584 | 585 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v5.0.2...v5.0.3) 586 | 587 | ## [v5.0.2](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v5.0.2) (2017-03-01) 588 | 589 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v5.0.1...v5.0.2) 590 | 591 | ## [v5.0.1](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v5.0.1) (2017-03-01) 592 | 593 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v5.0.0...v5.0.1) 594 | 595 | **Closed issues:** 596 | 597 | - Blowing up Mongoose with undefined value in $push [\#167](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/167) 598 | - Potential security issue with 409 error [\#166](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/166) 599 | 600 | **Merged pull requests:** 601 | 602 | - Parsing fix [\#170](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/170) ([ekryski](https://github.com/ekryski)) 603 | 604 | ## [v5.0.0](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v5.0.0) (2017-02-27) 605 | 606 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v4.0.1...v5.0.0) 607 | 608 | **Closed issues:** 609 | 610 | - Please provide a working example for $push [\#165](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/165) 611 | - Virtuals [\#164](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/164) 612 | - How can I pass additional query parameters in a REST `put` operation? [\#162](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/162) 613 | 614 | **Merged pull requests:** 615 | 616 | - adding better mongoose conflict error handling [\#168](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/168) ([ekryski](https://github.com/ekryski)) 617 | 618 | ## [v4.0.1](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v4.0.1) (2017-02-03) 619 | 620 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v4.0.0...v4.0.1) 621 | 622 | **Merged pull requests:** 623 | 624 | - Use toObject for create when lean is true [\#160](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/160) ([daffl](https://github.com/daffl)) 625 | 626 | ## [v4.0.0](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v4.0.0) (2017-01-29) 627 | 628 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v3.6.2...v4.0.0) 629 | 630 | **Closed issues:** 631 | 632 | - Make lean: true by default [\#132](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/132) 633 | 634 | **Merged pull requests:** 635 | 636 | - Make lean true by default [\#159](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/159) ([daffl](https://github.com/daffl)) 637 | 638 | ## [v3.6.2](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v3.6.2) (2017-01-13) 639 | 640 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v3.6.1...v3.6.2) 641 | 642 | **Closed issues:** 643 | 644 | - using lean options in a service with a subdocument model doesn't seem to work [\#157](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/157) 645 | - Cast to ObjectId failed for value \_id [\#156](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/156) 646 | 647 | **Merged pull requests:** 648 | 649 | - Allow $select to catch and use string syntax [\#158](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/158) ([jamesjnadeau](https://github.com/jamesjnadeau)) 650 | - Added warning msg when options.lean is falsey [\#155](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/155) ([eddyystop](https://github.com/eddyystop)) 651 | 652 | ## [v3.6.1](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v3.6.1) (2016-11-15) 653 | 654 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v3.6.0...v3.6.1) 655 | 656 | **Closed issues:** 657 | 658 | - feathers-mongoose@3.6.0 break custom model id [\#151](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/151) 659 | - Disable $where by default [\#148](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/148) 660 | 661 | **Merged pull requests:** 662 | 663 | - Do not delete model ids [\#152](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/152) ([daffl](https://github.com/daffl)) 664 | 665 | ## [v3.6.0](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v3.6.0) (2016-11-12) 666 | 667 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v3.5.3...v3.6.0) 668 | 669 | **Fixed bugs:** 670 | 671 | - patch doesn't throw correct object [\#110](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/110) 672 | 673 | **Closed issues:** 674 | 675 | - Patch is deleting subdocument data [\#144](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/144) 676 | - How do I get average? [\#140](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/140) 677 | - Support $select in get [\#134](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/134) 678 | - Add changelog binary to repo [\#114](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/114) 679 | 680 | **Merged pull requests:** 681 | 682 | - Update feathers-service-tests to version 0.9.0 🚀 [\#150](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/150) ([greenkeeperio-bot](https://github.com/greenkeeperio-bot)) 683 | - feathers-rest@1.5.2 breaks build ⚠️ [\#147](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/147) ([greenkeeperio-bot](https://github.com/greenkeeperio-bot)) 684 | - Node 4+ [\#146](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/146) ([daffl](https://github.com/daffl)) 685 | - babel-core@6.18.2 breaks build ⚠️ [\#143](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/143) ([greenkeeperio-bot](https://github.com/greenkeeperio-bot)) 686 | - babel-cli@6.18.0 breaks build ⚠️ [\#138](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/138) ([greenkeeperio-bot](https://github.com/greenkeeperio-bot)) 687 | - jshint —\> semistandard [\#131](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/131) ([corymsmith](https://github.com/corymsmith)) 688 | - adding code coverage [\#130](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/130) ([ekryski](https://github.com/ekryski)) 689 | - Update feathers-service-tests to version 0.8.1 🚀 [\#124](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/124) ([greenkeeperio-bot](https://github.com/greenkeeperio-bot)) 690 | 691 | ## [v3.5.3](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v3.5.3) (2016-10-14) 692 | 693 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v3.5.2...v3.5.3) 694 | 695 | **Closed issues:** 696 | 697 | - Add MongoDB requirement information to README [\#128](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/128) 698 | - Query filter does not work with booleans [\#122](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/122) 699 | - Extending rest api functionality [\#121](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/121) 700 | - $populate across multiple levels [\#119](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/119) 701 | - Cannot get REST response to work [\#118](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/118) 702 | - $or query with a REST GET request \($or needs an array\) [\#117](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/117) 703 | 704 | **Merged pull requests:** 705 | 706 | - Adds the posibility to send an object to the $select [\#129](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/129) ([stalinb87](https://github.com/stalinb87)) 707 | 708 | ## [v3.5.2](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v3.5.2) (2016-08-23) 709 | 710 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v3.5.1...v3.5.2) 711 | 712 | **Fixed bugs:** 713 | 714 | - Cannot $populate on update or patch [\#111](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/111) 715 | 716 | **Closed issues:** 717 | 718 | - Soft Delete [\#109](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/109) 719 | - Routing Feathers service with express [\#108](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/108) 720 | - Wrong ObjectId passed to service methods on nested routes [\#106](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/106) 721 | - How to change response code and data [\#102](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/102) 722 | 723 | **Merged pull requests:** 724 | 725 | - Ensure we convert mongoose models to regular objects. Closes \#110. [\#112](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/112) ([ekryski](https://github.com/ekryski)) 726 | - Update mocha to version 3.0.0 🚀 [\#107](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/107) ([greenkeeperio-bot](https://github.com/greenkeeperio-bot)) 727 | 728 | ## [v3.5.1](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v3.5.1) (2016-07-17) 729 | 730 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v3.5.0...v3.5.1) 731 | 732 | **Fixed bugs:** 733 | 734 | - Duplicate key errors should be Conflicts not BadRequests [\#104](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/104) 735 | 736 | **Merged pull requests:** 737 | 738 | - changing duplicate key error to feathers Conflict error. Closes \#104 [\#105](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/105) ([ekryski](https://github.com/ekryski)) 739 | - Update feathers-query-filters to version 2.0.0 🚀 [\#101](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/101) ([greenkeeperio-bot](https://github.com/greenkeeperio-bot)) 740 | 741 | ## [v3.5.0](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v3.5.0) (2016-07-09) 742 | 743 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v3.4.2...v3.5.0) 744 | 745 | **Fixed bugs:** 746 | 747 | - We shouldn't remove properties from original objects [\#98](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/98) 748 | 749 | **Merged pull requests:** 750 | 751 | - feathers-service-tests@0.6.2 breaks build 🚨 [\#100](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/100) ([greenkeeperio-bot](https://github.com/greenkeeperio-bot)) 752 | 753 | ## [v3.4.2](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v3.4.2) (2016-07-07) 754 | 755 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v3.4.1...v3.4.2) 756 | 757 | **Closed issues:** 758 | 759 | - Update README.md to reflect change to 'name' option for the service. [\#96](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/96) 760 | - Can't use `$inc` in patch [\#95](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/95) 761 | - Is this possible for multiple insert using Feathers mongoose? [\#94](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/94) 762 | 763 | **Merged pull requests:** 764 | 765 | - Adding Context:'query' so validators get the updating document [\#99](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/99) ([quick691fr](https://github.com/quick691fr)) 766 | - Update README.md [\#97](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/97) ([githugger](https://github.com/githugger)) 767 | - mongoose@4.5.2 breaks build 🚨 [\#93](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/93) ([greenkeeperio-bot](https://github.com/greenkeeperio-bot)) 768 | 769 | ## [v3.4.1](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v3.4.1) (2016-06-21) 770 | 771 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v3.4.0...v3.4.1) 772 | 773 | **Closed issues:** 774 | 775 | - Support passing mongoose specific params to queries. [\#70](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/70) 776 | - Add a way to $push in a PATCH [\#68](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/68) 777 | - Handle duplicate key errors as a special case [\#67](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/67) 778 | 779 | **Merged pull requests:** 780 | 781 | - Wrapping native mongoldb errors. Specifically duplicate key errors. [\#92](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/92) ([ekryski](https://github.com/ekryski)) 782 | - Adding support to be able to do $push, $set, etc. on patch [\#91](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/91) ([ekryski](https://github.com/ekryski)) 783 | 784 | ## [v3.4.0](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v3.4.0) (2016-06-17) 785 | 786 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v3.3.7...v3.4.0) 787 | 788 | **Closed issues:** 789 | 790 | - \_id is a string [\#89](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/89) 791 | - Embedded documents validation using model validation [\#88](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/88) 792 | - Cast to ObjectId failed for value X at path "\_id" [\#81](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/81) 793 | - $populate documentation [\#75](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/75) 794 | - Change remove-all syntax. [\#74](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/74) 795 | - update operations don't fail if required keys are missing. [\#73](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/73) 796 | - Support $search [\#50](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/50) 797 | 798 | **Merged pull requests:** 799 | 800 | - Update feathers-service-tests to version 0.6.0 🚀 [\#90](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/90) ([greenkeeperio-bot](https://github.com/greenkeeperio-bot)) 801 | - Adding runValidators [\#87](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/87) ([marshallswain](https://github.com/marshallswain)) 802 | - mocha@2.5.0 breaks build 🚨 [\#86](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/86) ([greenkeeperio-bot](https://github.com/greenkeeperio-bot)) 803 | - Update babel-plugin-add-module-exports to version 0.2.0 🚀 [\#84](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/84) ([greenkeeperio-bot](https://github.com/greenkeeperio-bot)) 804 | 805 | ## [v3.3.7](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v3.3.7) (2016-04-21) 806 | 807 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v3.3.6...v3.3.7) 808 | 809 | **Fixed bugs:** 810 | 811 | - $select doesn't work in find? [\#71](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/71) 812 | 813 | **Closed issues:** 814 | 815 | - hooks.toObject\(\) fails if data is paginated [\#77](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/77) 816 | - Bypass of methods [\#76](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/76) 817 | - custom query [\#69](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/69) 818 | 819 | **Merged pull requests:** 820 | 821 | - Fix for toObject hook [\#78](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/78) ([jack-guy](https://github.com/jack-guy)) 822 | - Fixed overwrite option: was always true [\#72](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/72) ([leo-nard](https://github.com/leo-nard)) 823 | 824 | ## [v3.3.6](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v3.3.6) (2016-02-24) 825 | 826 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v3.3.5...v3.3.6) 827 | 828 | **Merged pull requests:** 829 | 830 | - bumping feathers-errors version [\#66](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/66) ([ekryski](https://github.com/ekryski)) 831 | 832 | ## [v3.3.5](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v3.3.5) (2016-02-23) 833 | 834 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v3.3.4...v3.3.5) 835 | 836 | **Merged pull requests:** 837 | 838 | - enforcing that you shouldn't be able to change ids [\#65](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/65) ([ekryski](https://github.com/ekryski)) 839 | 840 | ## [v3.3.4](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v3.3.4) (2016-02-23) 841 | 842 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v3.3.3...v3.3.4) 843 | 844 | **Closed issues:** 845 | 846 | - Updating/Patching with custom ids results in orphaned documents. [\#63](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/63) 847 | 848 | **Merged pull requests:** 849 | 850 | - custom ids no longer get deleted on patch and update. Closes \#63 [\#64](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/64) ([ekryski](https://github.com/ekryski)) 851 | 852 | ## [v3.3.3](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v3.3.3) (2016-02-23) 853 | 854 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v3.3.2...v3.3.3) 855 | 856 | **Closed issues:** 857 | 858 | - Error handler should reject not throw [\#61](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/61) 859 | 860 | **Merged pull requests:** 861 | 862 | - Convert errorHandler to return a rejected promise instead of throwing [\#62](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/62) ([daffl](https://github.com/daffl)) 863 | 864 | ## [v3.3.2](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v3.3.2) (2016-02-22) 865 | 866 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v3.3.1...v3.3.2) 867 | 868 | **Merged pull requests:** 869 | 870 | - Example update [\#60](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/60) ([ekryski](https://github.com/ekryski)) 871 | 872 | ## [v3.3.1](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v3.3.1) (2016-02-20) 873 | 874 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v3.3.0...v3.3.1) 875 | 876 | **Closed issues:** 877 | 878 | - option.id ignored in \_get [\#58](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/58) 879 | - What is the best way for validate mongoose models? [\#57](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/57) 880 | - Validation errors return an HTTP 500 Error [\#56](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/56) 881 | - how to catch MongoError [\#55](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/55) 882 | - Documentation on troubleshooting [\#31](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/31) 883 | 884 | **Merged pull requests:** 885 | 886 | - get should use the options.id attribute. Closes \#58 [\#59](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/59) ([ekryski](https://github.com/ekryski)) 887 | 888 | ## [v3.3.0](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v3.3.0) (2016-02-12) 889 | 890 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v3.2.0...v3.3.0) 891 | 892 | **Closed issues:** 893 | 894 | - $populate options does not work in get\(\) queries [\#53](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/53) 895 | 896 | **Merged pull requests:** 897 | 898 | - Implement $populate option in params.query on get\(\) [\#54](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/54) ([BigAB](https://github.com/BigAB)) 899 | 900 | ## [v3.2.0](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v3.2.0) (2016-02-09) 901 | 902 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v3.1.1...v3.2.0) 903 | 904 | **Closed issues:** 905 | 906 | - use .lean\(\) liberally in fetch's? [\#51](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/51) 907 | - Document using $regex [\#49](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/49) 908 | - Adding fields on update isn't currently possible [\#48](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/48) 909 | - toObject\(\) hook should check for presence of .toObject\(\) [\#44](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/44) 910 | - With toObject\(\), ObjectIDs aren't stringified. [\#43](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/43) 911 | 912 | **Merged pull requests:** 913 | 914 | - Running lean [\#52](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/52) ([ekryski](https://github.com/ekryski)) 915 | 916 | ## [v3.1.1](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v3.1.1) (2016-01-30) 917 | 918 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v3.1.0...v3.1.1) 919 | 920 | **Merged pull requests:** 921 | 922 | - Make result counting optional and enable only for pagination [\#47](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/47) ([daffl](https://github.com/daffl)) 923 | 924 | ## [v3.1.0](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v3.1.0) (2016-01-30) 925 | 926 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v3.0.4...v3.1.0) 927 | 928 | **Merged pull requests:** 929 | 930 | - Use internal methods instead of service methods directly [\#46](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/46) ([daffl](https://github.com/daffl)) 931 | - Remove array check in create. [\#45](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/45) ([marshallswain](https://github.com/marshallswain)) 932 | 933 | ## [v3.0.4](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v3.0.4) (2016-01-08) 934 | 935 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v3.0.3...v3.0.4) 936 | 937 | **Implemented enhancements:** 938 | 939 | - Support batch creates [\#21](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/21) 940 | - Support batch updates [\#20](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/20) 941 | 942 | **Merged pull requests:** 943 | 944 | - Documenting the toObject hook. [\#41](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/41) ([marshallswain](https://github.com/marshallswain)) 945 | 946 | ## [v3.0.3](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v3.0.3) (2016-01-08) 947 | 948 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v3.0.2...v3.0.3) 949 | 950 | **Closed issues:** 951 | 952 | - $populate is broken [\#40](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/40) 953 | 954 | ## [v3.0.2](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v3.0.2) (2016-01-08) 955 | 956 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v3.0.1...v3.0.2) 957 | 958 | **Implemented enhancements:** 959 | 960 | - Support replica sets [\#19](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/19) 961 | 962 | **Closed issues:** 963 | 964 | - Docs for extending are wrong [\#39](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/39) 965 | - Named export 'service' should expose constructor function, not init function [\#37](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/37) 966 | - No documentation for Error handling [\#28](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/28) 967 | - Add documentation for use with feathers-hooks [\#25](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/25) 968 | 969 | ## [v3.0.1](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v3.0.1) (2016-01-08) 970 | 971 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v3.0.0...v3.0.1) 972 | 973 | **Closed issues:** 974 | 975 | - ES6 export doesn't work with module export plugin [\#35](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/35) 976 | 977 | **Merged pull requests:** 978 | 979 | - Fix default module export for ES5 environments [\#36](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/36) ([daffl](https://github.com/daffl)) 980 | 981 | ## [v3.0.0](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v3.0.0) (2016-01-04) 982 | 983 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v2.0.0...v3.0.0) 984 | 985 | **Implemented enhancements:** 986 | 987 | - Auto-generate documentation from schema [\#9](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/9) 988 | - Validation error should be returned as JSON [\#6](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/6) 989 | 990 | **Closed issues:** 991 | 992 | - Add docs for overriding the service methods \(patch for example\) [\#34](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/34) 993 | - NPM Release for feathers-hooks [\#29](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/29) 994 | - Events? [\#27](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/27) 995 | - How to override service methods [\#26](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/26) 996 | - Utilize query.lean\(\) to get plain objects. [\#24](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/24) 997 | - Document using virtuals. [\#23](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/23) 998 | 999 | **Merged pull requests:** 1000 | 1001 | - Update to ES6 [\#33](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/33) ([ekryski](https://github.com/ekryski)) 1002 | - Add toObject hook for documents. [\#32](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/32) ([marshallswain](https://github.com/marshallswain)) 1003 | - delete app.configure\(feathers.errors\(\)\) [\#30](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/30) ([AminBK](https://github.com/AminBK)) 1004 | - Add feathers-hooks compatibility. [\#22](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/22) ([marshallswain](https://github.com/marshallswain)) 1005 | 1006 | ## [v2.0.0](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v2.0.0) (2015-08-05) 1007 | 1008 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/2.0.0-pre.1...v2.0.0) 1009 | 1010 | **Implemented enhancements:** 1011 | 1012 | - Validation [\#2](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/2) 1013 | - Support for relationships [\#1](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/1) 1014 | 1015 | **Closed issues:** 1016 | 1017 | - Should use underscores to donate filter params vs. params that are on a model [\#17](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/17) 1018 | - Update examples for 1.0.0 and add example for extension [\#15](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/15) 1019 | 1020 | **Merged pull requests:** 1021 | 1022 | - Release 2.0.0 [\#18](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/18) ([ekryski](https://github.com/ekryski)) 1023 | 1024 | ## [2.0.0-pre.1](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/2.0.0-pre.1) (2014-07-19) 1025 | 1026 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v1.2.0...2.0.0-pre.1) 1027 | 1028 | **Implemented enhancements:** 1029 | 1030 | - Rename to feathers-mongoose [\#14](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/14) 1031 | 1032 | **Closed issues:** 1033 | 1034 | - Allow overriding default CRUD methods [\#12](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/12) 1035 | 1036 | ## [v1.2.0](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v1.2.0) (2014-06-06) 1037 | 1038 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/v1.1.0...v1.2.0) 1039 | 1040 | ## [v1.1.0](https://github.com/feathersjs-ecosystem/feathers-mongoose/tree/v1.1.0) (2014-04-23) 1041 | 1042 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-mongoose/compare/2ee46bc05956ddcc7754cb015d85b7bc4a10b337...v1.1.0) 1043 | 1044 | **Implemented enhancements:** 1045 | 1046 | - Getter methods for Mongoose schema and model [\#10](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/10) 1047 | - Add `feathers-plugin` to keywords of package.json [\#8](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/8) 1048 | - Limited peerDependencies versions [\#7](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/7) 1049 | - Searching \(READ ALL\) [\#4](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/4) 1050 | - Sorting [\#3](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/3) 1051 | 1052 | **Fixed bugs:** 1053 | 1054 | - Mongoose TypeError when creating Schema [\#5](https://github.com/feathersjs-ecosystem/feathers-mongoose/issues/5) 1055 | 1056 | **Merged pull requests:** 1057 | 1058 | - Version bump and badges fix [\#13](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/13) ([Glavin001](https://github.com/Glavin001)) 1059 | - Cleaned up a few things [\#11](https://github.com/feathersjs-ecosystem/feathers-mongoose/pull/11) ([agonbina](https://github.com/agonbina)) 1060 | 1061 | 1062 | 1063 | \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* 1064 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Feathers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # feathers-mongoose 2 | 3 | [![CI](https://github.com/feathersjs-ecosystem/feathers-mongoose/workflows/CI/badge.svg)](https://github.com/feathersjs-ecosystem/feathers-mongoose/actions?query=workflow%3ACI) 4 | [![Dependency Status](https://img.shields.io/david/feathersjs-ecosystem/feathers-mongoose.svg?style=flat-square)](https://david-dm.org/feathersjs-ecosystem/feathers-mongoose) 5 | [![Download Status](https://img.shields.io/npm/dm/feathers-mongoose.svg?style=flat-square)](https://www.npmjs.com/package/feathers-mongoose) 6 | 7 | A [Feathers](https://feathersjs.com) database adapter for [Mongoose](http://mongoosejs.com/), an object modeling tool for [MongoDB](https://www.mongodb.org/). 8 | 9 | ```bash 10 | $ npm install --save mongoose feathers-mongoose 11 | ``` 12 | 13 | > __Important:__ `feathers-mongoose` implements the [Feathers Common database adapter API](https://docs.feathersjs.com/api/databases/common.html) and [querying syntax](https://docs.feathersjs.com/api/databases/querying.html). 14 | 15 | > This adapter also requires a [running MongoDB](https://docs.mongodb.com/getting-started/shell/#) database server. 16 | 17 | 18 | ## API 19 | 20 | ### `service(options)` 21 | 22 | Returns a new service instance initialized with the given options. `Model` has to be a Mongoose model. See the [Mongoose Guide](http://mongoosejs.com/docs/guide.html) for more information on defining your model. 23 | 24 | ```js 25 | const mongoose = require('mongoose'); 26 | const service = require('feathers-mongoose'); 27 | 28 | // A module that exports your Mongoose model 29 | const Model = require('./models/message'); 30 | 31 | // Make Mongoose use the ES6 promise 32 | mongoose.Promise = global.Promise; 33 | 34 | // Connect to a local database called `feathers` 35 | mongoose.connect('mongodb://localhost:27017/feathers'); 36 | 37 | app.use('/messages', service({ Model })); 38 | app.use('/messages', service({ Model, lean, id, events, paginate })); 39 | ``` 40 | 41 | __Options:__ 42 | 43 | - `Model` (**required**) - The Mongoose model definition 44 | - `lean` (*optional*, default: `true`) - Runs queries faster by returning plain objects instead of Mongoose models. 45 | - `id` (*optional*, default: `'_id'`) - The name of the id field property. 46 | - `events` (*optional*) - A list of [custom service events](https://docs.feathersjs.com/api/events.html#custom-events) sent by this service 47 | - `paginate` (*optional*) - A [pagination object](https://docs.feathersjs.com/api/databases/common.html#pagination) containing a `default` and `max` page size 48 | - `whitelist` (*optional*) - A list of additional query parameters to allow (e..g `[ '$regex', '$populate' ]`) 49 | - `multi` (*optional*) - Allow `create` with arrays and `update` and `remove` with `id` `null` to change multiple items. Can be `true` for all methods or an array of allowed methods (e.g. `[ 'remove', 'create' ]`) 50 | - `overwrite` (*optional*, default: `true`) - Overwrite the document when update, making mongoose detect is new document and trigger default value for unspecified properties in mongoose schema. 51 | - `discriminators` (*optional*) - A list of mongoose models that inherit from `Model`. 52 | - `useEstimatedDocumentCount` (*optional*, default: `false`) - Use `estimatedDocumentCount` instead (usually not necessary) 53 | - `queryModifier` (*optional*) - A function that takes in the raw mongoose Query object and params, which modifies all find and get requests unless overridden. (see Query Modifiers below) 54 | - `queryModifierKey` (*optional*, default: `'queryModifier'`) - The key to use to get the override query modifier function from the params. (see Query Modifiers below) 55 | 56 | > **Important:** To avoid odd error handling behaviour, always set `mongoose.Promise = global.Promise`. If not available already, Feathers comes with a polyfill for native Promises. 57 | 58 | 59 | 60 | > **Important:** When setting `lean` to `false`, Mongoose models will be returned which can not be modified unless they are converted to a regular JavaScript object via `toObject`. 61 | 62 | 63 | 64 | > **Note:** You can get access to the Mongoose model via `this.Model` inside a [hook](https://docs.feathersjs.com/api/hooks.html) and use it as usual. See the [Mongoose Guide](http://mongoosejs.com/docs/guide.html) for more information on defining your model. 65 | 66 | ### params.mongoose 67 | 68 | When making a [service method](https://docs.feathersjs.com/api/services.html) call, `params` can contain a `mongoose` property which allows you to modify the options used to run the Mongoose query. Normally, this will be set in a before [hook](https://docs.feathersjs.com/api/hooks.html): 69 | 70 | ```js 71 | app.service('messages').hooks({ 72 | before: { 73 | patch(context) { 74 | // Set some additional Mongoose options 75 | // The adapter tries to use these settings by defaults 76 | // but they can always be changed here 77 | context.params.mongoose = { 78 | runValidators: true, 79 | setDefaultsOnInsert: true 80 | } 81 | } 82 | } 83 | }); 84 | ``` 85 | 86 | The `mongoose` property is also useful for performing upserts on a `patch` request. "Upserts" do an update if a matching record is found, or insert a record if there's no existing match. The following example will create a document that matches the `data`, or if there's already a record that matches the `params.query`, that record will be updated. 87 | 88 | Using the `writeResult` mongoose option will return the write result of a `patch` operation, including the _ids of all upserted or modified documents. This can be helpful alongside the `upsert` flag, for detecting whether the outcome was a find or insert operation. More on write results is available in the [Mongo documentation](https://docs.mongodb.com/manual/reference/method/db.collection.update/#writeresult) 89 | 90 | ```js 91 | const data = { address: '123', identifier: 'my-identifier' } 92 | const params = { 93 | query: { address: '123' }, 94 | mongoose: { upsert: true, writeResult: true } 95 | } 96 | app.service('address-meta').patch(null, data, params) 97 | ``` 98 | 99 | 100 | ## Example 101 | 102 | Here's a complete example of a Feathers server with a `messages` Mongoose service. 103 | 104 | ``` 105 | $ npm install @feathersjs/feathers @feathersjs/errors @feathersjs/express @feathersjs/socketio mongoose feathers-mongoose 106 | ``` 107 | 108 | In `message-model.js`: 109 | 110 | ```js 111 | const mongoose = require('mongoose'); 112 | 113 | const Schema = mongoose.Schema; 114 | const MessageSchema = new Schema({ 115 | text: { 116 | type: String, 117 | required: true 118 | } 119 | }); 120 | const Model = mongoose.model('Message', MessageSchema); 121 | 122 | module.exports = Model; 123 | ``` 124 | 125 | Then in `app.js`: 126 | 127 | ```js 128 | const feathers = require('@feathersjs/feathers'); 129 | const express = require('@feathersjs/express'); 130 | const socketio = require('@feathersjs/socketio'); 131 | 132 | const mongoose = require('mongoose'); 133 | const service = require('feathers-mongoose'); 134 | 135 | const Model = require('./message-model'); 136 | 137 | mongoose.Promise = global.Promise; 138 | 139 | // Connect to your MongoDB instance(s) 140 | mongoose.connect('mongodb://localhost:27017/feathers'); 141 | 142 | // Create an Express compatible Feathers application instance. 143 | const app = express(feathers()); 144 | 145 | // Turn on JSON parser for REST services 146 | app.use(express.json()); 147 | // Turn on URL-encoded parser for REST services 148 | app.use(express.urlencoded({extended: true})); 149 | // Enable REST services 150 | app.configure(express.rest()); 151 | // Enable Socket.io services 152 | app.configure(socketio()); 153 | // Connect to the db, create and register a Feathers service. 154 | app.use('/messages', service({ 155 | Model, 156 | lean: true, // set to false if you want Mongoose documents returned 157 | paginate: { 158 | default: 2, 159 | max: 4 160 | } 161 | })); 162 | app.use(express.errorHandler()); 163 | 164 | // Create a dummy Message 165 | app.service('messages').create({ 166 | text: 'Message created on server' 167 | }).then(function(message) { 168 | console.log('Created message', message); 169 | }); 170 | 171 | // Start the server. 172 | const port = 3030; 173 | app.listen(port, () => { 174 | console.log(`Feathers server listening on port ${port}`); 175 | }); 176 | ``` 177 | 178 | You can run this example by using `node app` and go to [localhost:3030/messages](http://localhost:3030/messages). 179 | 180 | ## Querying, Validation 181 | 182 | Mongoose by default gives you the ability to add [validations at the model level](http://mongoosejs.com/docs/validation.html). Using an error handler like the one that [comes with Feathers](https://github.com/feathersjs/feathers-errors/blob/master/src/error-handler.js) your validation errors will be formatted nicely right out of the box! 183 | 184 | For more information on querying and validation refer to the [Mongoose documentation](http://mongoosejs.com/docs/guide.html). 185 | 186 | ## $populate 187 | 188 | For Mongoose, the special `$populate` query parameter can be used to allow [Mongoose query population](http://mongoosejs.com/docs/populate.html). 189 | 190 | > **Important:** `$populate` has to be whitelisted explicitly since it can expose protected fields in sub-documents (like the user password) which have to be removed manually. 191 | 192 | ```js 193 | const mongoose = require('feathers-mongoose'); 194 | 195 | app.use('/posts', mongoose({ 196 | Model, 197 | whitelist: [ '$populate' ] 198 | }); 199 | 200 | app.service('posts').find({ 201 | query: { $populate: 'user' } 202 | }); 203 | ``` 204 | 205 | ## Error handling 206 | 207 | As of v7.3.0, the original Mongoose error can be retrieved on the server via: 208 | 209 | ```js 210 | const { ERROR } = require('feathers-mongoose'); 211 | 212 | try { 213 | await app.service('posts').create({ value: 'invalid' }); 214 | } catch(error) { 215 | // error is a FeathersError 216 | // Safely retrieve the original Mongoose error 217 | const mongooseError = error[ERROR]; 218 | } 219 | ``` 220 | 221 | 222 | ## Discriminators (Inheritance) 223 | 224 | Instead of strict inheritance, Mongoose uses [discriminators](http://mongoosejs.com/docs/discriminators.html) as their schema inheritance model. 225 | To use them, pass in a `discriminatorKey` option to your schema object and use `Model.discriminator('modelName', schema)` instead of `mongoose.model()` 226 | 227 | Feathers comes with full support for mongoose discriminators, allowing for automatic fetching of inherited types. A typical use case might look like: 228 | 229 | ```js 230 | var mongoose = require('mongoose'); 231 | var Schema = mongoose.Schema; 232 | var Post = require('./post'); 233 | var feathers = require('@feathersjs/feathers'); 234 | var app = feathers(); 235 | var service = require('feathers-mongoose'); 236 | 237 | // Discriminator key, we'll use this later to refer to all text posts 238 | var options = { 239 | discriminatorKey: '_type' 240 | }; 241 | 242 | var TextPostSchema = new Schema({ 243 | text: { type: String, default: null } 244 | }, options); 245 | 246 | // Note the use of `Post.discriminator` rather than `mongoose.discriminator`. 247 | var TextPost = Post.discriminator('text', TextPostSchema); 248 | 249 | // Using the discriminators option, let feathers know about any inherited models you may have 250 | // for that service 251 | app.use('/posts', service({ 252 | Model: Post, 253 | discriminators: [TextPost] 254 | })) 255 | 256 | ``` 257 | 258 | Without support for discriminators, when you perform a `.get` on the posts service, you'd only get back `Post` models, not `TextPost` models. 259 | Now in your query, you can specify a value for your discriminatorKey: 260 | 261 | ```js 262 | { 263 | _type: 'text' 264 | } 265 | ``` 266 | 267 | and Feathers will automatically swap in the correct model and execute the query it instead of its parent model. 268 | 269 | ## Collation Support 270 | 271 | This adapter includes support for [collation and case insensitive indexes available in MongoDB v3.4](https://docs.mongodb.com/manual/release-notes/3.4/#collation-and-case-insensitive-indexes). Collation parameters may be passed using the special `collation` parameter to the `find()`, `remove()` and `patch()` methods. 272 | 273 | ### Example: Patch records with case-insensitive alphabetical ordering 274 | 275 | The example below would patch all student records with grades of `'c'` or `'C'` and above (a natural language ordering). Without collations this would not be as simple, since the comparison `{ $gt: 'c' }` would not include uppercase grades of `'C'` because the code point of `'C'` is less than that of `'c'`. 276 | 277 | ```js 278 | const patch = { shouldStudyMore: true }; 279 | const query = { grade: { $gte: 'c' } }; 280 | const collation = { locale: 'en', strength: 1 }; 281 | students.patch(null, patch, { query, collation }).then( ... ); 282 | ``` 283 | 284 | ### Example: Find records with a case-insensitive search 285 | 286 | Similar to the above example, this would find students with a grade of `'c'` or greater, in a case-insensitive manner. 287 | 288 | ```js 289 | const query = { grade: { $gte: 'c' } }; 290 | const collation = { locale: 'en', strength: 1 }; 291 | students.find({ query, collation }).then( ... ); 292 | ``` 293 | 294 | For more information on MongoDB's collation feature, visit the [collation reference page](https://docs.mongodb.com/manual/reference/collation/). 295 | 296 | 297 | ## Mongo-DB Transaction 298 | 299 | This adapter includes support to enable database transaction to rollback the persisted records for any error occured for a api call. This requires [Mongo-DB v4.x](https://docs.mongodb.com/manual/) installed and [replica-set](https://linode.com/docs/databases/mongodb/create-a-mongodb-replica-set/#start-replication-and-add-members) enabled. 300 | 301 | Start working with transaction enabled by adding the following lines in `app.hooks.js` or `.hooks.js`. 302 | 303 | ```js 304 | const TransactionManager = require('feathers-mongoose').TransactionManager; 305 | const isTransactionEnable = process.env.TRANSACTION_ENABLE || false; 306 | const skipPath = ['login']; 307 | 308 | let moduleExports = { 309 | before: { 310 | all: [], 311 | find: [], 312 | get: [], 313 | create: [ 314 | when(isTransactionEnable, async hook => 315 | TransactionManager.beginTransaction(hook, skipPath) 316 | ) 317 | ], 318 | update: [ 319 | when(isTransactionEnable, async hook => 320 | TransactionManager.beginTransaction(hook, skipPath) 321 | ) 322 | ], 323 | patch: [], 324 | remove: [] 325 | }, 326 | 327 | after: { 328 | all: [], 329 | find: [], 330 | get: [], 331 | create: [when(isTransactionEnable, TransactionManager.commitTransaction)], 332 | update: [when(isTransactionEnable, TransactionManager.commitTransaction)], 333 | patch: [], 334 | remove: [] 335 | }, 336 | 337 | error: { 338 | all: [], 339 | find: [], 340 | get: [], 341 | create: [when(isTransactionEnable, TransactionManager.rollbackTransaction)], 342 | update: [when(isTransactionEnable, TransactionManager.rollbackTransaction)], 343 | patch: [], 344 | remove: [] 345 | } 346 | }; 347 | 348 | module.exports = moduleExports; 349 | ``` 350 | 351 | ## Query Modifiers 352 | 353 | Sometimes it's important to use an unusual Mongoose Query method, like [specifying whether to read from a primary or secondary node,](https://mongoosejs.com/docs/api.html#query_Query-read) but maybe only for certain requests. 354 | 355 | You can access the internal Mongoose Query object used for a find/get request by specifying the queryModifier function. It is also possible to override that global function by specifying the function in a requests params. 356 | 357 | ```js 358 | // Specify a global query modifier when creating the service 359 | app.use('/messages', service({ 360 | Model, 361 | queryModifier: (query, params) => { 362 | query.read('secondaryPreferred'); 363 | } 364 | })); 365 | 366 | app.service('messages').find({ 367 | query: { ... }, 368 | }).then((result) => { 369 | console.log('Result from secondary:', result) 370 | }); 371 | 372 | // Override the modifier on a per-request basis 373 | app.service('messages').find({ 374 | query: { ... }, 375 | queryModifier: (query, params) => { 376 | query.read('primaryPreferred'); 377 | } 378 | }).then((result) => { 379 | console.log('Result from primary:', result) 380 | }); 381 | 382 | // Disable the global modifier on a per-request basis 383 | app.service('messages').find({ 384 | query: { ... }, 385 | queryModifier: false 386 | }).then((result) => { 387 | console.log('Result from default option:', result) 388 | }); 389 | ``` 390 | 391 | > **Note:** Due to replication lag, a secondary node can have "stale" data. You should ensure that this "staleness" will not be an issue for your application before reading from the secondary set. 392 | 393 | ## Contributing 394 | 395 | This module is community maintained and open for pull requests. Features and bug fixes should contain 396 | 397 | - The bug fix / feature code 398 | - Tests to reproduce the bug or test the feature 399 | - Documentation updates (if necessary) 400 | 401 | To contribute, fork and clone the repository. To run the tests, a MongoDB v4.0.0 server is required. If you do not have a MongoDB server running you can start one with: 402 | 403 | ``` 404 | npm run mongodb 405 | ``` 406 | 407 | The command needs to stay open while running the tests with 408 | 409 | 410 | ``` 411 | npm test 412 | ``` 413 | 414 | ## License 415 | 416 | [MIT](LICENSE) 417 | 418 | ## Authors 419 | 420 | - [Feathers contributors](https://github.com/feathersjs-ecosystem/feathers-mongoose/graphs/contributors) 421 | -------------------------------------------------------------------------------- /lib/error-handler.js: -------------------------------------------------------------------------------- 1 | const errors = require('@feathersjs/errors'); 2 | const ERROR = Symbol('feathers-mongoose/error'); 3 | 4 | const wrap = (error, original) => Object.assign(error, { [ERROR]: original }); 5 | 6 | exports.ERROR = ERROR; 7 | 8 | exports.errorHandler = (error) => { 9 | if (error.code === 11000 || error.code === 11001) { 10 | // NOTE (EK): Error parsing as discussed in this github thread 11 | // https://github.com/Automattic/mongoose/issues/2129 12 | const match1 = error.message.match(/_?([a-zA-Z]*)_?\d?\s*dup key/i); 13 | const match2 = error.message.match(/\s*dup key:\s*\{\s*:\s*"?(.*?)"?\s*\}/i); 14 | 15 | const key = match1 ? match1[1] : 'path'; 16 | let value = match2 ? match2[1] : 'value'; 17 | 18 | if (value === 'null') { 19 | value = null; 20 | } else if (value === 'undefined') { 21 | value = undefined; 22 | } 23 | 24 | error.message = `${key}: ${value} already exists.`; 25 | error.errors = { 26 | [key]: value 27 | }; 28 | 29 | return Promise.reject(wrap(new errors.Conflict(error), error)); 30 | } 31 | 32 | if (error.name) { 33 | switch (error.name) { 34 | case 'ValidationError': 35 | case 'ValidatorError': 36 | case 'CastError': 37 | case 'VersionError': 38 | return Promise.reject(wrap(new errors.BadRequest(error), error)); 39 | case 'OverwriteModelError': 40 | return Promise.reject(wrap(new errors.Conflict(error), error)); 41 | case 'MissingSchemaError': 42 | case 'DivergentArrayError': 43 | return Promise.reject(wrap(new errors.GeneralError(error), error)); 44 | case 'MongoError': 45 | return Promise.reject(wrap(new errors.GeneralError(error), error)); 46 | } 47 | } 48 | 49 | return Promise.reject(error); 50 | }; 51 | -------------------------------------------------------------------------------- /lib/hooks.js: -------------------------------------------------------------------------------- 1 | exports.toObject = function (options = {}, dataField = 'data') { 2 | return function (hook) { 3 | // Only perform this if it's used as an after hook. 4 | if (hook.result) { 5 | const data = hook.result[dataField] || hook.result; 6 | let res; 7 | 8 | // Handle multiple mongoose models 9 | if (Array.isArray(data)) { 10 | res = data.map((obj) => { 11 | if (typeof obj.toObject === 'function') { 12 | return obj.toObject(options); 13 | } 14 | 15 | return obj; 16 | }); 17 | } else if (typeof data.toObject === 'function') { // Handle single mongoose models 18 | res = data.toObject(options); 19 | } 20 | // If our data is transformed set it to appropriate location on the hook 21 | if (res) { 22 | if (hook.result[dataField]) { 23 | hook.result[dataField] = res; 24 | } else { 25 | hook.result = res; 26 | } 27 | } 28 | } 29 | }; 30 | }; 31 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | const hooks = require('./hooks'); 2 | const service = require('./service'); 3 | const transactionManager = require('./transaction-manager'); 4 | 5 | Object.assign(service, { hooks, service }); 6 | 7 | module.exports = service; 8 | module.exports.TransactionManager = transactionManager; 9 | -------------------------------------------------------------------------------- /lib/service.js: -------------------------------------------------------------------------------- 1 | const { _ } = require('@feathersjs/commons'); 2 | const { AdapterService, select } = require('@feathersjs/adapter-commons'); 3 | const errors = require('@feathersjs/errors'); 4 | 5 | const { ERROR, errorHandler } = require('./error-handler'); 6 | 7 | // Create the service. 8 | class Service extends AdapterService { 9 | constructor (options) { 10 | if (!options.Model || !options.Model.modelName) { 11 | throw new Error('You must provide a Mongoose Model'); 12 | } 13 | 14 | const { whitelist = ['$regex'] } = options; 15 | 16 | super(Object.assign({ 17 | id: '_id', 18 | filters: Object.assign({ 19 | $populate (value) { 20 | return value; 21 | } 22 | }, options.filters), 23 | queryModifierKey: 'queryModifier' 24 | }, options, { 25 | whitelist: whitelist.concat('$and') 26 | })); 27 | 28 | this.discriminatorKey = this.Model.schema.options.discriminatorKey; 29 | this.discriminators = {}; 30 | (options.discriminators || []).forEach(element => { 31 | if (element.modelName) { 32 | this.discriminators[element.modelName] = element; 33 | } 34 | }); 35 | this.lean = options.lean === undefined ? true : options.lean; 36 | this.overwrite = options.overwrite !== false; 37 | this.useEstimatedDocumentCount = !!options.useEstimatedDocumentCount; 38 | } 39 | 40 | get Model () { 41 | return this.options.Model; 42 | } 43 | 44 | _getQueryModifier (params) { 45 | if (typeof params[this.options.queryModifierKey] === 'function') { 46 | return params[this.options.queryModifierKey]; 47 | } 48 | if (params[this.options.queryModifierKey] !== false) { 49 | if (typeof this.options.queryModifier === 'function') { 50 | return this.options.queryModifier; 51 | } 52 | } 53 | return () => {}; 54 | } 55 | 56 | _getOrFind (id, params = {}) { 57 | if (id === null) { 58 | return this._find(params); 59 | } 60 | 61 | return this._get(id, params); 62 | } 63 | 64 | _find (params = {}) { 65 | const { filters, query, paginate } = this.filterQuery(params); 66 | const discriminator = (params.query || {})[this.discriminatorKey] || this.discriminatorKey; 67 | const model = this.discriminators[discriminator] || this.Model; 68 | const q = model.find(query).lean(this.lean); 69 | 70 | // $select uses a specific find syntax, so it has to come first. 71 | if (Array.isArray(filters.$select)) { 72 | q.select(filters.$select.reduce((res, key) => Object.assign(res, { 73 | [key]: 1 74 | }), {})); 75 | } else if (typeof filters.$select === 'string' || typeof filters.$select === 'object') { 76 | q.select(filters.$select); 77 | } 78 | 79 | // Handle $sort 80 | if (filters.$sort) { 81 | q.sort(filters.$sort); 82 | } 83 | 84 | // Handle collation 85 | if (params.collation) { 86 | q.collation(params.collation); 87 | } 88 | 89 | // Handle $limit 90 | if (typeof filters.$limit !== 'undefined') { 91 | q.limit(filters.$limit); 92 | } 93 | 94 | // Handle $skip 95 | if (filters.$skip) { 96 | q.skip(filters.$skip); 97 | } 98 | 99 | // Handle $populate 100 | if (filters.$populate && this.options.whitelist.includes('$populate')) { 101 | q.populate(filters.$populate); 102 | } 103 | 104 | this._getQueryModifier(params)(q, params); 105 | 106 | let executeQuery = total => q.session(params.mongoose && params.mongoose.session).exec().then(data => { 107 | return { 108 | total, 109 | limit: filters.$limit, 110 | skip: filters.$skip || 0, 111 | data 112 | }; 113 | }); 114 | 115 | if (filters.$limit === 0) { 116 | executeQuery = total => Promise.resolve({ 117 | total, 118 | limit: filters.$limit, 119 | skip: filters.$skip || 0, 120 | data: [] 121 | }); 122 | } 123 | 124 | if (paginate && paginate.default) { 125 | return model.where(query)[this.useEstimatedDocumentCount ? 'estimatedDocumentCount' : 'countDocuments']() 126 | .session(params.mongoose && params.mongoose.session).exec().then(executeQuery); 127 | } 128 | 129 | return executeQuery().then(page => page.data); 130 | } 131 | 132 | _get (id, params = {}) { 133 | const { query, filters } = this.filterQuery(params); 134 | 135 | query.$and = (query.$and || []).concat([{ [this.id]: id }]); 136 | 137 | const discriminator = query[this.discriminatorKey] || this.discriminatorKey; 138 | const model = this.discriminators[discriminator] || this.Model; 139 | let modelQuery = model.findOne(query); 140 | 141 | // Handle $populate 142 | if (filters.$populate && this.options.whitelist.includes('$populate')) { 143 | modelQuery = modelQuery.populate(filters.$populate); 144 | } 145 | 146 | // Handle $select 147 | if (filters.$select && filters.$select.length) { 148 | const fields = { [this.id]: 1 }; 149 | 150 | for (const key of filters.$select) { 151 | fields[key] = 1; 152 | } 153 | 154 | modelQuery.select(fields); 155 | } else if (filters.$select && typeof filters.$select === 'object') { 156 | modelQuery.select(filters.$select); 157 | } 158 | 159 | this._getQueryModifier(params)(modelQuery, params); 160 | 161 | return modelQuery.session(params.mongoose && params.mongoose.session) 162 | .lean(this.lean).exec().then(data => { 163 | if (!data) { 164 | throw new errors.NotFound(`No record found for id '${id}'`); 165 | } 166 | 167 | return data; 168 | }).catch(errorHandler); 169 | } 170 | 171 | _create (_data, params = {}) { 172 | const discriminator = (params.query || {})[this.discriminatorKey] || this.discriminatorKey; 173 | const model = this.discriminators[discriminator] || this.Model; 174 | const { query: { $populate } = {} } = params; 175 | const isMulti = Array.isArray(_data); 176 | const data = isMulti ? _data : [_data]; 177 | 178 | return model.create(data, params.mongoose).then(results => { 179 | if (results === undefined) { 180 | return []; 181 | } 182 | if ($populate && this.options.whitelist.includes('$populate')) { 183 | return Promise.all(results.map(result => this.Model.populate(result, $populate))); 184 | } 185 | 186 | return results; 187 | }).then(results => { 188 | if (this.lean) { 189 | results = results.map(item => (item.toObject ? item.toObject() : item)); 190 | } 191 | 192 | return isMulti ? results : results[0]; 193 | }).then(select(params, this.id)).catch(errorHandler); 194 | } 195 | 196 | _update (id, data, params = {}) { 197 | if (id === null) { 198 | return Promise.reject(new errors.BadRequest('Not replacing multiple records. Did you mean `patch`?')); 199 | } 200 | 201 | // Handle case where data might be a mongoose model 202 | if (typeof data.toObject === 'function') { 203 | data = data.toObject(); 204 | } 205 | 206 | const { query, filters } = this.filterQuery(params); 207 | const options = Object.assign({ 208 | new: true, 209 | overwrite: this.overwrite, 210 | runValidators: true, 211 | context: 'query', 212 | setDefaultsOnInsert: true 213 | }, params.mongoose); 214 | 215 | query.$and = (query.$and || []).concat({ [this.id]: id }); 216 | 217 | if (this.id === '_id') { 218 | // We can not update default mongo ids 219 | data = _.omit(data, this.id); 220 | } else { 221 | // If not using the default Mongo _id field set the id to its 222 | // previous value. This prevents orphaned documents. 223 | data = Object.assign({}, data, { [this.id]: id }); 224 | } 225 | 226 | const discriminator = query[this.discriminatorKey] || this.discriminatorKey; 227 | const model = this.discriminators[discriminator] || this.Model; 228 | let modelQuery = model.findOneAndUpdate(query, data, options); 229 | 230 | if (filters.$populate && this.options.whitelist.includes('$populate')) { 231 | modelQuery = modelQuery.populate(filters.$populate); 232 | } 233 | 234 | return modelQuery.lean(this.lean).exec() 235 | .then(result => { 236 | if (result === null) { 237 | throw new errors.NotFound(`No record found for id '${id}'`); 238 | } 239 | 240 | return result; 241 | }) 242 | .then(select(params, this.id)).catch(errorHandler); 243 | } 244 | 245 | _patch (id, data, params = {}) { 246 | const { query } = this.filterQuery(params); 247 | const mapIds = data => Array.isArray(data) ? data.map(current => current[this.id]) : [data[this.id]]; 248 | 249 | // By default we will just query for the one id. For multi patch 250 | // we create a list of the ids of all items that will be changed 251 | // to re-query them after the update 252 | const ids = this._getOrFind(id, Object.assign({}, params, { 253 | paginate: false 254 | })).then(mapIds); 255 | 256 | // Handle case where data might be a mongoose model 257 | if (typeof data.toObject === 'function') { 258 | data = data.toObject(); 259 | } 260 | 261 | // ensure we are working on a copy 262 | data = Object.assign({}, data); 263 | 264 | // If we are updating multiple records 265 | const options = Object.assign({ 266 | multi: id === null, 267 | runValidators: true, 268 | context: 'query' 269 | }, params.mongoose); 270 | 271 | if (id !== null) { 272 | query.$and = (query.$and || []).concat({ [this.id]: id }); 273 | } 274 | 275 | if (this.id === '_id') { 276 | // We can not update default mongo ids 277 | delete data[this.id]; 278 | } else if (id !== null) { 279 | // If not using the default Mongo _id field set the id to its 280 | // previous value. This prevents orphaned documents. 281 | data[this.id] = id; 282 | } 283 | 284 | // NOTE (EK): We need this shitty hack because update doesn't 285 | // return a promise properly when runValidators is true. WTF! 286 | try { 287 | return ids.then(idList => { 288 | const { query: { $populate } = {} } = params; 289 | // Create a new query that re-queries all ids that 290 | // were originally changed 291 | const updatedQuery = { [this.id]: { $in: idList } }; 292 | const findParams = Object.assign({}, params, { 293 | paginate: false, 294 | query: $populate && this.options.whitelist.includes('$populate') ? Object.assign(updatedQuery, { $populate }) : updatedQuery 295 | }); 296 | 297 | // If params.query.$populate was provided, remove it 298 | // from the query sent to mongoose. 299 | const discriminator = query[this.discriminatorKey] || this.discriminatorKey; 300 | const model = this.discriminators[discriminator] || this.Model; 301 | return model 302 | .updateMany(query, data, options) 303 | .lean(this.lean) 304 | .exec() 305 | .then(writeResult => { 306 | if (options.writeResult) { 307 | return writeResult; 308 | } 309 | if (writeResult.upsertedCount > 0) { 310 | return this._getOrFind(id, Object.assign({}, params, { paginate: false })); 311 | } 312 | 313 | if ('upserted' in writeResult) { 314 | return this._getOrFind(id, Object.assign({}, params, { query: { [this.id]: { $in: writeResult.upserted.map(doc => doc._id) } } }, { paginate: false })); 315 | } 316 | 317 | return this._getOrFind(id, findParams); 318 | }); 319 | }).then(select(params, this.id)).catch(errorHandler); 320 | } catch (e) { 321 | return errorHandler(e); 322 | } 323 | } 324 | 325 | _remove (id, params = {}) { 326 | const { query } = this.filterQuery(params); 327 | 328 | if (params.collation) { 329 | query.collation = params.collation; 330 | } 331 | 332 | const findParams = Object.assign({}, params, { 333 | paginate: false, 334 | query 335 | }); 336 | 337 | if (id !== null) { 338 | query.$and = (query.$and || []).concat({ [this.id]: id }); 339 | } 340 | 341 | // NOTE (EK): First fetch the record(s) so that we can return 342 | // it/them when we delete it/them. 343 | return this._getOrFind(id, findParams).then((data) => { 344 | if (id !== null) { 345 | return this.Model.deleteOne(query, params.mongoose) 346 | .lean(this.lean) 347 | .exec() 348 | .then(() => data) 349 | .then(select(params, this.id)); 350 | } 351 | 352 | return this.Model.deleteMany(query, params.mongoose) 353 | .lean(this.lean) 354 | .exec() 355 | .then(() => data) 356 | .then(select(params, this.id)); 357 | }).catch(errorHandler); 358 | } 359 | } 360 | 361 | function init (options) { 362 | return new Service(options); 363 | } 364 | 365 | module.exports = Object.assign(init, { 366 | default: init, 367 | ERROR, 368 | Service 369 | }); 370 | -------------------------------------------------------------------------------- /lib/transaction-manager.js: -------------------------------------------------------------------------------- 1 | /** 2 | * To start a new session and initiate transaction on the session and set 3 | * mongoose-session in context-params to all consecutive service call if the 4 | * boolean 5 | * 6 | * @param {object} context context and all params 7 | * @param {[string]} skipPath list of paths to exclude from transaction 8 | * - Example: ['login'] 9 | * @return {object} context context with db-session appended 10 | */ 11 | const beginTransaction = async (context, skipPath = []) => { 12 | const client = context.app.get('mongoDbClient') || context.service.Model.db; 13 | try { 14 | // if the current path is not added to skipPath-list 15 | if (skipPath.indexOf(context.path) === -1) { 16 | // if there is no open-transaction appended already 17 | if (context.params && !context.params.transactionOpen) { 18 | const session = await client.startSession(); 19 | await session.startTransaction(); 20 | context.params.transactionOpen = true; 21 | context.params.mongoose = { ...context.params.mongoose, session }; 22 | } 23 | context.enableTransaction = true; // true if transaction is enabled 24 | } else { 25 | context.enableTransaction = false; 26 | } 27 | return context; 28 | } catch (err) { 29 | throw new Error(`Error while starting session ${err}`); 30 | } 31 | }; 32 | 33 | /** 34 | * To commit a mongo-transaction after save methods of mongo service 35 | * 36 | * @param {object} context context with params, result and DB-session 37 | * @return {object} context context with params, result and DB-session 38 | */ 39 | const commitTransaction = async context => { 40 | try { 41 | // if transaction is enabled during startSession 42 | if (context.enableTransaction) { 43 | // if context contains the mongoose session to be committed 44 | if ( 45 | context.params && 46 | context.params.mongoose && 47 | context.params.mongoose.session 48 | ) { 49 | await context.params.mongoose.session.commitTransaction(); 50 | context.params.mongoose = null; 51 | context.params.transactionOpen = false; // reset transaction-open 52 | context.enableTransaction = false; 53 | } 54 | } 55 | return context; 56 | } catch (err) { 57 | throw new Error(`Error while commiting transaction ${err}`); 58 | } 59 | }; 60 | 61 | /** 62 | * To rollback a mongo-transaction for any error thrown in service calls 63 | * 64 | * @param {object} context context with params and DB-session 65 | * @return {object} context context with params and DB-session 66 | */ 67 | const rollbackTransaction = async context => { 68 | try { 69 | // if transaction is enabled during startSession 70 | if (context.enableTransaction) { 71 | // if context contains the mongoose session to be committed 72 | if ( 73 | context.params && 74 | context.params.mongoose && 75 | context.params.mongoose.session 76 | ) { 77 | await context.params.mongoose.session.abortTransaction(); 78 | context.params.mongoose = null; 79 | context.params.transactionOpen = false; // reset transaction-open 80 | context.enableTransaction = false; 81 | } 82 | } 83 | return context; 84 | } catch (err) { 85 | throw new Error(`Error while rolling-back transaction ${err}`); 86 | } 87 | }; 88 | 89 | module.exports = { 90 | beginTransaction, 91 | commitTransaction, 92 | rollbackTransaction 93 | }; 94 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "feathers-mongoose", 3 | "description": "A Feathers service adapter for the Mongoose ORM", 4 | "version": "8.5.1", 5 | "homepage": "https://github.com/feathersjs-ecosystem/feathers-mongoose", 6 | "main": "lib/", 7 | "types": "types", 8 | "keywords": [ 9 | "feathers", 10 | "feathers-plugin", 11 | "REST", 12 | "Socket.io", 13 | "realtime", 14 | "mongodb", 15 | "mongo", 16 | "mongoose", 17 | "service" 18 | ], 19 | "license": "MIT", 20 | "repository": { 21 | "type": "git", 22 | "url": "git://github.com/feathersjs-ecosystem/feathers-mongoose.git" 23 | }, 24 | "author": { 25 | "name": "Feathers contributors", 26 | "email": "hello@feathersjs.com", 27 | "url": "https://feathersjs.com" 28 | }, 29 | "contributors": [ 30 | "Eric Kryski (http://erickryski.com)", 31 | "Glavin Wiechert (https://github.com/Glavin001)", 32 | "Marshall Thompson (https://github.com/marshallswain)" 33 | ], 34 | "bugs": { 35 | "url": "https://github.com/feathersjs-ecosystem/feathers-mongoose/issues" 36 | }, 37 | "engines": { 38 | "node": ">= 12" 39 | }, 40 | "files": [ 41 | "CHANGELOG.md", 42 | "LICENSE", 43 | "README.md", 44 | "lib/**", 45 | "types/**", 46 | "*.d.ts", 47 | "*.js" 48 | ], 49 | "scripts": { 50 | "publish": "git push origin --tags && npm run changelog && git push origin", 51 | "changelog": "github_changelog_generator --user feathersjs-ecosystem --project feathers-mongoose && git add CHANGELOG.md && git commit -am \"Updating changelog\"", 52 | "release:patch": "npm version patch && npm publish", 53 | "release:minor": "npm version minor && npm publish", 54 | "release:major": "npm version major && npm publish", 55 | "mongodb": "run-rs -v 4.0.0", 56 | "lint": "semistandard --fix", 57 | "dtslint": "dtslint types", 58 | "mocha": "mocha --timeout 5000 --recursive test/ --exit", 59 | "update-dependencies": "ncu -u", 60 | "coverage": "nyc npm run mocha", 61 | "test": "npm run lint && npm run coverage" 62 | }, 63 | "semistandard": { 64 | "env": [ 65 | "mocha" 66 | ] 67 | }, 68 | "directories": { 69 | "lib": "lib" 70 | }, 71 | "peerDependencies": { 72 | "mongoose": ">=6.0.14" 73 | }, 74 | "dependencies": { 75 | "@feathersjs/adapter-commons": "^4.5.15", 76 | "@feathersjs/commons": "^4.5.15", 77 | "@feathersjs/errors": "^4.5.15" 78 | }, 79 | "devDependencies": { 80 | "@feathersjs/adapter-tests": "^4.5.15", 81 | "@feathersjs/express": "^4.5.15", 82 | "@feathersjs/feathers": "^4.5.15", 83 | "@feathersjs/socketio": "^4.5.15", 84 | "chai": "^4.3.6", 85 | "dtslint": "^4.2.1", 86 | "mocha": "^10.0.0", 87 | "mongoose": "^6.5.4", 88 | "npm-check-updates": "^16.0.6", 89 | "nyc": "^15.1.0", 90 | "run-rs": "^0.7.7", 91 | "semistandard": "^16.0.1", 92 | "sinon": "^14.0.0", 93 | "sinon-chai": "^3.7.0", 94 | "typescript": "^4.8.2" 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /test/error-handler.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-expressions */ 2 | const { expect } = require('chai'); 3 | const mongoose = require('mongoose'); 4 | const errors = require('@feathersjs/errors'); 5 | 6 | const { ERROR, errorHandler } = require('../lib/error-handler'); 7 | 8 | describe('Feathers Mongoose Error Handler', () => { 9 | it('throws a feathers error', async () => { 10 | const e = new errors.GeneralError(); 11 | 12 | try { 13 | await errorHandler(e); 14 | throw new Error('Should never get here'); 15 | } catch (error) { 16 | expect(error).to.be.an.instanceof(errors.GeneralError); 17 | } 18 | }); 19 | 20 | it('wraps a ValidationError as a BadRequest', async () => { 21 | const e = new errors.GeneralError(); 22 | 23 | e.name = 'ValidationError'; 24 | e.errors = {}; 25 | 26 | try { 27 | await errorHandler(e); 28 | throw new Error('Should never get here'); 29 | } catch (error) { 30 | expect(error).to.be.an.instanceof(errors.BadRequest); 31 | } 32 | }); 33 | 34 | it('preserves a validation error message', async () => { 35 | const e = new errors.GeneralError(); 36 | 37 | e.name = 'ValidationError'; 38 | e.errors = {}; 39 | e.message = 'Invalid Email'; 40 | 41 | try { 42 | await errorHandler(e); 43 | throw new Error('Should never get here'); 44 | } catch (error) { 45 | expect(error.message).to.equal('Invalid Email'); 46 | } 47 | }); 48 | 49 | it('preserves a validation errors', async () => { 50 | const emailError = { 51 | email: { 52 | message: 'email cannot be null', 53 | type: 'notNull Violation', 54 | path: 'email', 55 | value: null 56 | } 57 | }; 58 | 59 | const e = new errors.GeneralError(); 60 | 61 | e.name = 'ValidationError'; 62 | e.errors = {}; 63 | e.errors = emailError; 64 | 65 | try { 66 | await errorHandler(e); 67 | } catch (error) { 68 | expect(error.errors).to.deep.equal(emailError); 69 | } 70 | }); 71 | 72 | it('wraps a ValidatorError as a BadRequest', async () => { 73 | const e = new errors.GeneralError(); 74 | 75 | e.name = 'ValidationError'; 76 | e.errors = {}; 77 | 78 | try { 79 | await errorHandler(e); 80 | throw new Error('Should never get here'); 81 | } catch (error) { 82 | expect(error).to.be.an.instanceof(errors.BadRequest); 83 | } 84 | }); 85 | 86 | it('wraps a CastError as a BadRequest', async () => { 87 | const e = new mongoose.Error.CastError(); 88 | 89 | try { 90 | await errorHandler(e); 91 | throw new Error('Should never get here'); 92 | } catch (error) { 93 | expect(error).to.be.an.instanceof(errors.BadRequest); 94 | } 95 | }); 96 | 97 | it('wraps a VersionError as a BadRequest', async () => { 98 | const e = new mongoose.Error.VersionError({ _id: 'testing' }, null, []); 99 | 100 | try { 101 | await errorHandler(e); 102 | throw new Error('Should never get here'); 103 | } catch (error) { 104 | expect(error).to.be.an.instanceof(errors.BadRequest); 105 | } 106 | }); 107 | 108 | it('wraps a OverwriteModelError as a Conflict', async () => { 109 | const e = new mongoose.Error.OverwriteModelError(); 110 | 111 | try { 112 | await errorHandler(e); 113 | throw new Error('Should never get here'); 114 | } catch (error) { 115 | expect(error).to.be.an.instanceof(errors.Conflict); 116 | } 117 | }); 118 | 119 | it('wraps a MissingSchemaError as a GeneralError', async () => { 120 | const e = new mongoose.Error.MissingSchemaError(); 121 | 122 | try { 123 | await errorHandler(e); 124 | throw new Error('Should never get here'); 125 | } catch (error) { 126 | expect(error).to.be.an.instanceof(errors.GeneralError); 127 | } 128 | }); 129 | 130 | it('wraps a DivergentArrayError as a GeneralError', async () => { 131 | const fn = function () {}; 132 | const e = new mongoose.Error.DivergentArrayError({ join: fn }); 133 | 134 | try { 135 | await errorHandler(e); 136 | throw new Error('Should never get here'); 137 | } catch (error) { 138 | expect(error).to.be.an.instanceof(errors.GeneralError); 139 | } 140 | }); 141 | 142 | describe('DuplicateKey error', () => { 143 | it('gets wrapped as a Conflict error', async () => { 144 | const e = Error('E11000 duplicate key error collection: db.users index: name_1 dup key: { : "Kate" }'); 145 | e.name = 'MongoError'; 146 | e.code = 11000; 147 | 148 | try { 149 | await errorHandler(e); 150 | throw new Error('Should never get here'); 151 | } catch (error) { 152 | expect(error).to.be.an.instanceof(errors.Conflict); 153 | } 154 | }); 155 | 156 | it('has the correct error message #1', async () => { 157 | const e = Error('E11000 duplicate key error collection: db.users index: name_1 dup key: { : "Kate" }'); 158 | e.name = 'MongoError'; 159 | e.code = 11000; 160 | 161 | try { 162 | await errorHandler(e); 163 | throw new Error('Should never get here'); 164 | } catch (error) { 165 | expect(error.message).to.equal('name: Kate already exists.'); 166 | } 167 | }); 168 | 169 | it('has the correct error message #2', async () => { 170 | const e = Error("E11000 duplicate key error index: myDb.myCollection.$id dup key: { : ObjectId('57226808ec55240c00000272') }"); 171 | e.name = 'MongoError'; 172 | e.code = 11000; 173 | 174 | try { 175 | await errorHandler(e); 176 | throw new Error('Should never get here'); 177 | } catch (error) { 178 | expect(error.message).to.equal('id: ObjectId(\'57226808ec55240c00000272\') already exists.'); 179 | } 180 | }); 181 | 182 | it('has the correct errors object #1', async () => { 183 | const e = Error('E11000 duplicate key error index: test.collection.$a.b_1 dup key: { : null }'); 184 | e.name = 'MongoError'; 185 | e.code = 11000; 186 | 187 | try { 188 | await errorHandler(e); 189 | throw new Error('Should never get here'); 190 | } catch (error) { 191 | expect(error.errors).to.deep.equal({ b: null }); 192 | } 193 | }); 194 | 195 | it('has the correct errors object #2', async () => { 196 | const e = Error('E11000 duplicate key error collection: db.users index: name_1 dup key: { : "Kate" }'); 197 | e.name = 'MongoError'; 198 | e.code = 11000; 199 | 200 | try { 201 | await errorHandler(e); 202 | throw new Error('Should never get here'); 203 | } catch (error) { 204 | expect(error.errors).to.deep.equal({ name: 'Kate' }); 205 | } 206 | }); 207 | 208 | it('returns the original error', async () => { 209 | const e = new Error('E11000 duplicate key error collection: db.users index: name_1 dup key: { : "Kate" }'); 210 | e.name = 'MongoError'; 211 | e.code = 11000; 212 | 213 | try { 214 | await errorHandler(e); 215 | throw new Error('Should never get here'); 216 | } catch (error) { 217 | expect(error[ERROR]).to.deep.equal(e); 218 | } 219 | }); 220 | }); 221 | }); 222 | -------------------------------------------------------------------------------- /test/hooks.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-expressions */ 2 | const chai = require('chai'); 3 | const sinon = require('sinon'); 4 | const sinonChai = require('sinon-chai'); 5 | 6 | const { hooks } = require('../lib'); 7 | 8 | const expect = chai.expect; 9 | chai.use(sinonChai); 10 | 11 | describe('Feathers Mongoose Hooks', () => { 12 | describe('toObject', () => { 13 | describe('options', () => { 14 | let toObject, hook; 15 | 16 | beforeEach(() => { 17 | toObject = sinon.spy(); 18 | hook = { 19 | result: { toObject } 20 | }; 21 | }); 22 | 23 | it('sets default options', () => { 24 | hooks.toObject()(hook); 25 | expect(toObject).to.be.calledWith({}); 26 | }); 27 | 28 | it('supports custom options', () => { 29 | const options = { feathers: 'awesome' }; 30 | hooks.toObject(options)(hook); 31 | expect(toObject).to.be.calledWith(options); 32 | }); 33 | }); 34 | 35 | describe('when results are mongoose model(s)', () => { 36 | let user1, user2, users; 37 | 38 | beforeEach(() => { 39 | user1 = { 40 | name: 'Jerry', 41 | age: 23, 42 | toObject: sinon.stub().returns({ name: 'Jerry', age: 23 }) 43 | }; 44 | 45 | user2 = { 46 | name: 'Mary', 47 | age: 32, 48 | toObject: sinon.stub().returns({ name: 'Mary', age: 32 }) 49 | }; 50 | 51 | users = [user1, user2]; 52 | }); 53 | 54 | it('converts paginated arrays of mongoose models', () => { 55 | const hook = { 56 | result: { data: users } 57 | }; 58 | 59 | hooks.toObject()(hook); 60 | expect(users[0].toObject).to.be.calledOnce; 61 | expect(users[1].toObject).to.be.calledOnce; 62 | expect(hook.result.data[0]).to.deep.equal({ name: 'Jerry', age: 23 }); 63 | expect(hook.result.data[1]).to.deep.equal({ name: 'Mary', age: 32 }); 64 | }); 65 | 66 | it('converts a single mongoose model', () => { 67 | const hook = { 68 | result: users[0] 69 | }; 70 | 71 | hooks.toObject()(hook); 72 | expect(users[0].toObject).to.be.calledOnce; 73 | expect(hook.result).to.deep.equal({ name: 'Jerry', age: 23 }); 74 | }); 75 | 76 | it('converts non-paginated arrays of mongoose models', () => { 77 | const hook = { 78 | result: users 79 | }; 80 | 81 | hooks.toObject()(hook); 82 | expect(users[0].toObject).to.be.calledOnce; 83 | expect(users[1].toObject).to.be.calledOnce; 84 | expect(hook.result[0]).to.deep.equal({ name: 'Jerry', age: 23 }); 85 | expect(hook.result[1]).to.deep.equal({ name: 'Mary', age: 32 }); 86 | }); 87 | }); 88 | 89 | describe('when results are plain object(s)', () => { 90 | let user1, user2, users; 91 | 92 | beforeEach(() => { 93 | user1 = { 94 | name: 'Jerry', 95 | age: 23 96 | }; 97 | 98 | user2 = { 99 | name: 'Mary', 100 | age: 32 101 | }; 102 | 103 | users = [user1, user2]; 104 | }); 105 | 106 | it('does not convert paginated arrays of objects', () => { 107 | const hook = { 108 | result: { data: users } 109 | }; 110 | 111 | hooks.toObject()(hook); 112 | expect(hook.result.data[0]).to.deep.equal(user1); 113 | expect(hook.result.data[1]).to.deep.equal(user2); 114 | }); 115 | 116 | it('does not convert non-paginated arrays of objects', () => { 117 | const hook = { 118 | result: users 119 | }; 120 | 121 | hooks.toObject({}, null)(hook); 122 | expect(hook.result[0]).to.deep.equal(user1); 123 | expect(hook.result[1]).to.deep.equal(user2); 124 | }); 125 | 126 | it('does not convert a single object', () => { 127 | const hook = { 128 | result: user1 129 | }; 130 | 131 | hooks.toObject()(hook); 132 | expect(hook.result).to.deep.equal(user1); 133 | }); 134 | }); 135 | }); 136 | }); 137 | -------------------------------------------------------------------------------- /test/index.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-expressions */ 2 | const { expect } = require('chai'); 3 | const mongoose = require('mongoose'); 4 | 5 | const errors = require('@feathersjs/errors'); 6 | const feathers = require('@feathersjs/feathers'); 7 | const adaptersTests = require('@feathersjs/adapter-tests'); 8 | 9 | const adapter = require('../lib'); 10 | const testSuite = adaptersTests([ 11 | '.options', 12 | '.events', 13 | '._get', 14 | '._find', 15 | '._create', 16 | '._update', 17 | '._patch', 18 | '._remove', 19 | '.get', 20 | '.get + $select', 21 | '.get + id + query', 22 | '.get + NotFound', 23 | '.find', 24 | '.remove', 25 | '.remove + $select', 26 | '.remove + id + query', 27 | '.remove + multi', 28 | '.update', 29 | '.update + $select', 30 | '.update + id + query', 31 | '.update + NotFound', 32 | '.patch', 33 | '.patch + $select', 34 | '.patch + id + query', 35 | '.patch multiple', 36 | '.patch multi query', 37 | '.patch + NotFound', 38 | '.create', 39 | '.create + $select', 40 | '.create multi', 41 | 'internal .find', 42 | 'internal .get', 43 | 'internal .create', 44 | 'internal .update', 45 | 'internal .patch', 46 | 'internal .remove', 47 | '.find + equal', 48 | '.find + equal multiple', 49 | '.find + $sort', 50 | '.find + $sort + string', 51 | '.find + $limit', 52 | '.find + $limit 0', 53 | '.find + $skip', 54 | '.find + $select', 55 | '.find + $or', 56 | '.find + $in', 57 | '.find + $nin', 58 | '.find + $lt', 59 | '.find + $lte', 60 | '.find + $gt', 61 | '.find + $gte', 62 | '.find + $ne', 63 | '.find + $gt + $lt + $sort', 64 | '.find + $or nested + $sort', 65 | '.find + paginate', 66 | '.find + paginate + $limit + $skip', 67 | '.find + paginate + $limit 0', 68 | '.find + paginate + params', 69 | '.get + id + query id', 70 | '.remove + id + query id', 71 | '.update + id + query id', 72 | '.patch + id + query id' 73 | ]); 74 | 75 | const { 76 | User, 77 | Pet, 78 | Peeps, 79 | CustomPeeps, 80 | Post, 81 | TextPost 82 | } = require('./models'); 83 | 84 | const _ids = {}; 85 | const _petIds = {}; 86 | const app = feathers() 87 | .use('/peeps', adapter({ 88 | Model: Peeps, 89 | events: ['testing'] 90 | })) 91 | .use('/peeps-customid', adapter({ 92 | id: 'customid', 93 | Model: CustomPeeps, 94 | events: ['testing'] 95 | })) 96 | .use('/people', adapter({ 97 | Model: User, 98 | lean: false, 99 | multi: true, 100 | whitelist: ['$populate'] 101 | })) 102 | .use('/pets', adapter({ 103 | Model: Pet, 104 | lean: false, 105 | multi: true, 106 | whitelist: ['$populate'] 107 | })) 108 | .use('/people2', adapter({ 109 | Model: User, 110 | multi: true, 111 | whitelist: ['$populate'] 112 | })) 113 | .use('/pets2', adapter({ 114 | Model: Pet, 115 | multi: true, 116 | whitelist: ['$populate'] 117 | })) 118 | .use('/pets3', adapter({ 119 | Model: Pet, 120 | multi: true, 121 | queryModifier: (query) => { 122 | query.where('type', 'cat'); 123 | } 124 | })) 125 | .use('/posts', adapter({ 126 | Model: Post, 127 | discriminators: [TextPost], 128 | multi: true, 129 | whitelist: ['$populate'] 130 | })); 131 | const people = app.service('people'); 132 | const pets = app.service('pets'); 133 | const leanPeople = app.service('people2'); 134 | const leanPets = app.service('pets2'); 135 | const QMPets = app.service('pets3'); 136 | const posts = app.service('posts'); 137 | 138 | describe('Feathers Mongoose Service', () => { 139 | // Connect to your MongoDB instance(s) 140 | before(() => mongoose.connect('mongodb://localhost:27017/feathers', { 141 | useNewUrlParser: true 142 | })); 143 | 144 | describe('Requiring', () => { 145 | const lib = require('../lib'); 146 | 147 | it('exposes the service as a default module', () => { 148 | expect(typeof lib).to.equal('function'); 149 | }); 150 | 151 | it('exposes the Service Constructor', () => { 152 | expect(typeof lib.Service).to.equal('function'); 153 | }); 154 | 155 | it('exposes hooks', () => { 156 | expect(typeof lib.hooks).to.equal('object'); 157 | }); 158 | }); 159 | 160 | describe('Initialization', () => { 161 | it('throws an error when missing a Model', () => { 162 | expect(adapter.bind(null, { name: 'Test' })).to.throw(/You must provide a Mongoose Model/); 163 | }); 164 | 165 | it('sets the default to be _id', () => { 166 | expect(people.id).to.equal('_id'); 167 | }); 168 | 169 | it('merges whitelist parameters (#347)', () => { 170 | expect(people.options.whitelist).to.deep.equal(['$populate', '$and']); 171 | }); 172 | 173 | it('when missing the overwrite option sets the default to be true', () => { 174 | expect(people.overwrite).to.be.true; 175 | }); 176 | 177 | it('when missing the lean option sets the default to be false', () => { 178 | expect(people.lean).to.be.false; 179 | }); 180 | }); 181 | 182 | describe('Special collation param', () => { 183 | function indexOfName (results, name) { 184 | let index; 185 | results.every(function (person, i) { 186 | if (person.name === name) { 187 | index = i; 188 | return false; 189 | } 190 | return true; 191 | }); 192 | return index; 193 | } 194 | 195 | beforeEach(() => { 196 | return people.remove(null, {}).then(() => { 197 | return people.create([ 198 | { name: 'AAA' }, 199 | { name: 'aaa' }, 200 | { name: 'ccc' } 201 | ]); 202 | }); 203 | }); 204 | 205 | it('sorts with default behavior without collation param', async () => { 206 | const r = await people.find({ query: { $sort: { name: -1 } } }); 207 | 208 | expect(indexOfName(r, 'aaa')).to.be.below(indexOfName(r, 'AAA')); 209 | }); 210 | 211 | // This appears to be a flaky test for some reason 212 | it.skip('sorts using collation param if present', async () => { 213 | const r = await people.find({ 214 | query: { $sort: { name: -1 } }, 215 | collation: { locale: 'en', strength: 1 } 216 | }); 217 | 218 | expect(indexOfName(r, 'AAA')).to.be.below(indexOfName(r, 'aaa')); 219 | }); 220 | 221 | it('removes with default behavior without collation param', async () => { 222 | await people.remove(null, { query: { name: { $gt: 'AAA' } } }); 223 | 224 | const r = await people.find(); 225 | 226 | expect(r).to.have.lengthOf(1); 227 | expect(r[0].name).to.equal('AAA'); 228 | }); 229 | 230 | it.skip('removes using collation param if present', async () => { 231 | await people.remove(null, { 232 | query: { name: { $gt: 'AAA' } }, 233 | collation: { locale: 'en', strength: 1 } 234 | }); 235 | 236 | const r = await people.find(); 237 | 238 | expect(r).to.have.lengthOf(3); 239 | }); 240 | 241 | it('updates with default behavior without collation param', async () => { 242 | const query = { name: { $gt: 'AAA' } }; 243 | const r = await people.patch(null, { age: 99 }, { query }); 244 | 245 | expect(r).to.have.lengthOf(2); 246 | r.forEach(person => { 247 | expect(person.age).to.equal(99); 248 | }); 249 | }); 250 | 251 | it('updates using collation param if present', async () => { 252 | const r = await people.patch(null, { age: 110 }, { 253 | query: { name: { $gt: 'AAA' } }, 254 | collation: { locale: 'en', strength: 1 } 255 | }); 256 | 257 | expect(r).to.have.lengthOf(1); 258 | expect(r[0].name).to.equal('ccc'); 259 | }); 260 | }); 261 | 262 | describe('Common functionality', () => { 263 | beforeEach(async () => { 264 | const pet = await pets.create({ type: 'dog', name: 'Rufus', gender: 'Unknown' }); 265 | 266 | _petIds.Rufus = pet._id; 267 | 268 | const user = await people.create({ 269 | name: 'Doug', 270 | age: 32, 271 | pets: [pet._id] 272 | }); 273 | 274 | _ids.Doug = user._id; 275 | }); 276 | 277 | afterEach(async () => { 278 | await pets.remove(null, { query: {} }); 279 | await people.remove(null, { query: {} }); 280 | }); 281 | 282 | it('can $select with a String', async () => { 283 | const params = { 284 | query: { 285 | name: 'Rufus', 286 | $select: '+gender' 287 | } 288 | }; 289 | 290 | const data = await pets.find(params); 291 | 292 | expect(data[0].gender).to.equal('Unknown'); 293 | }); 294 | 295 | it('can $select with an Array', async () => { 296 | const params = { 297 | query: { 298 | name: 'Rufus', 299 | $select: ['gender'] 300 | } 301 | }; 302 | 303 | const data = await pets.find(params); 304 | 305 | expect(data[0].gender).to.equal('Unknown'); 306 | }); 307 | 308 | it('can $select with an Object', async () => { 309 | const params = { 310 | query: { 311 | name: 'Rufus', 312 | $select: { gender: true } 313 | } 314 | }; 315 | 316 | const data = await pets.find(params); 317 | 318 | expect(data[0].gender).to.equal('Unknown'); 319 | }); 320 | 321 | it('can $populate with find', async () => { 322 | const params = { 323 | query: { 324 | name: 'Doug', 325 | $populate: ['pets'] 326 | } 327 | }; 328 | 329 | const data = await people.find(params); 330 | 331 | expect(data[0].pets[0].name).to.equal('Rufus'); 332 | }); 333 | 334 | it('can $populate with get', async () => { 335 | const params = { 336 | query: { 337 | $populate: ['pets'] 338 | } 339 | }; 340 | 341 | const data = await people.get(_ids.Doug, params); 342 | 343 | expect(data.pets[0].name).to.equal('Rufus'); 344 | }); 345 | 346 | it('can patch a mongoose model', async () => { 347 | const dougModel = await people.get(_ids.Doug); 348 | const data = await people.patch(_ids.Doug, dougModel); 349 | 350 | expect(data.name).to.equal('Doug'); 351 | }); 352 | 353 | it('can patch a mongoose model', async () => { 354 | const dougModel = await people.get(_ids.Doug); 355 | const data = await people.update(_ids.Doug, dougModel); 356 | 357 | expect(data.name).to.equal('Doug'); 358 | }); 359 | 360 | it('can upsert with patch', async () => { 361 | const data = { name: 'Henry', age: 300 }; 362 | const params = { 363 | mongoose: { upsert: true }, 364 | query: { name: 'Henry' } 365 | }; 366 | 367 | const result = await people.patch(null, data, params); 368 | 369 | expect(Array.isArray(result)).to.equal(true); 370 | 371 | const henry = result[0]; 372 | 373 | expect(henry.name).to.equal('Henry'); 374 | }); 375 | 376 | it('can upsert with patch & receive writeResult', async () => { 377 | const data = { name: 'John', age: 200 }; 378 | const params = { 379 | mongoose: { upsert: true, writeResult: true }, 380 | query: { name: 'John' } 381 | }; 382 | 383 | const results = await people.patch(null, data, params); 384 | 385 | expect(results).to.be.instanceOf(Object); 386 | expect(results).to.have.property('acknowledged', true); 387 | expect(results).to.have.property('upsertedCount', 1); 388 | }); 389 | 390 | it('can $populate with update', async () => { 391 | const params = { 392 | query: { 393 | $populate: ['pets'] 394 | } 395 | }; 396 | 397 | const doug = await people.get(_ids.Doug); 398 | const newDoug = doug.toObject(); 399 | 400 | newDoug.name = 'Bob'; 401 | 402 | const data = await people.update(_ids.Doug, newDoug, params); 403 | 404 | expect(data.name).to.equal('Bob'); 405 | expect(data.pets[0].name).to.equal('Rufus'); 406 | }); 407 | 408 | it('can $populate with patch', async () => { 409 | const params = { 410 | query: { 411 | $populate: ['pets'] 412 | } 413 | }; 414 | 415 | const data = await people.patch(_ids.Doug, { name: 'Bob' }, params); 416 | 417 | expect(data.name).to.equal('Bob'); 418 | expect(data.pets[0].name).to.equal('Rufus'); 419 | }); 420 | 421 | it('can $populate with .create (#268)', async () => { 422 | const params = { 423 | query: { 424 | $populate: ['pets'] 425 | } 426 | }; 427 | 428 | const user = await people.create({ name: 'Dougler', age: 3, pets: [_petIds.Rufus] }, params); 429 | 430 | expect(user.pets[0].name).to.equal('Rufus'); 431 | 432 | await people.remove(user._id); 433 | }); 434 | 435 | it('can $push an item onto an array with update', async () => { 436 | const margeaux = await pets.create({ type: 'cat', name: 'Margeaux' }); 437 | 438 | await people.update(_ids.Doug, { $push: { pets: margeaux } }); 439 | 440 | const params = { 441 | query: { 442 | $populate: ['pets'] 443 | } 444 | }; 445 | 446 | const data = await people.get(_ids.Doug, params); 447 | 448 | expect(data.pets[1].name).to.equal('Margeaux'); 449 | }); 450 | 451 | it('can $push an item onto an array with patch', async () => { 452 | const margeaux = await pets.create({ type: 'cat', name: 'Margeaux' }); 453 | 454 | await people.patch(_ids.Doug, { $push: { pets: margeaux } }); 455 | 456 | const params = { 457 | query: { 458 | $populate: ['pets'] 459 | } 460 | }; 461 | const data = await people.get(_ids.Doug, params); 462 | 463 | expect(data.pets[1].name).to.equal('Margeaux'); 464 | }); 465 | 466 | it('runs validators on update', async () => { 467 | const person = await people.create({ name: 'David', age: 33 }); 468 | 469 | try { 470 | await people.update(person._id, { name: 'Dada', age: 'wrong' }); 471 | throw new Error('Update should not be successful'); 472 | } catch (error) { 473 | expect(error.name).to.equal('BadRequest'); 474 | expect(error.message).to.equal('User validation failed: age: Cast to Number failed for value "wrong" (type string) at path "age"'); 475 | } 476 | }); 477 | 478 | it('runs validators on patch', async () => { 479 | const person = await people.create({ name: 'David', age: 33 }); 480 | 481 | try { 482 | await people.patch(person._id, { name: 'Dada', age: 'wrong' }); 483 | throw new Error('Update should not be successful'); 484 | } catch (error) { 485 | expect(error.name).to.equal('BadRequest'); 486 | expect(error.message).to.equal('Cast to Number failed for value "wrong" (type string) at path "age"'); 487 | } 488 | }); 489 | 490 | it('returns a Conflict when unique index is violated', async () => { 491 | try { 492 | await pets.create({ type: 'cat', name: 'Bob' }); 493 | await pets.create({ type: 'cat', name: 'Bob' }); 494 | throw new Error('Should not be successful'); 495 | } catch (error) { 496 | expect(error.name).to.equal('Conflict'); 497 | } 498 | }); 499 | 500 | it('Returns correct result when queried properties ar patched', async () => { 501 | const data = await pets.patch(null, { name: 'Spot' }, { query: { name: 'Rufus' } }); 502 | expect(data).to.be.an('array'); 503 | expect(data.length).to.equal(1); 504 | expect(data[0].name).to.equal('Spot'); 505 | }); 506 | }); 507 | 508 | describe('Lean Services', () => { 509 | beforeEach(async () => { 510 | const pet = await leanPets.create({ type: 'dog', name: 'Rufus' }); 511 | const user = await leanPeople.create({ name: 'Doug', age: 32, pets: [pet._id] }); 512 | 513 | _petIds.Rufus = pet._id; 514 | _ids.Doug = user._id; 515 | }); 516 | 517 | afterEach(async () => { 518 | await leanPets.remove(null, { query: {} }); 519 | await leanPeople.remove(null, { query: {} }); 520 | }); 521 | 522 | it('can $populate with find', async () => { 523 | const params = { 524 | query: { 525 | name: 'Doug', 526 | $populate: ['pets'] 527 | } 528 | }; 529 | 530 | const data = await leanPeople.find(params); 531 | 532 | expect(data[0].pets[0].name).to.equal('Rufus'); 533 | }); 534 | 535 | it('can $populate with get', async () => { 536 | const params = { 537 | query: { 538 | $populate: ['pets'] 539 | } 540 | }; 541 | 542 | const data = await leanPeople.get(_ids.Doug, params); 543 | 544 | expect(data.pets[0].name).to.equal('Rufus'); 545 | }); 546 | 547 | it('can upsert with patch', async () => { 548 | const data = { name: 'Henry', age: 300 }; 549 | const params = { 550 | mongoose: { upsert: true }, 551 | query: { name: 'Henry' } 552 | }; 553 | 554 | const results = await leanPeople.patch(null, data, params); 555 | 556 | expect(Array.isArray(results)).to.equal(true); 557 | 558 | const henry = results[0]; 559 | 560 | expect(henry.name).to.equal('Henry'); 561 | }); 562 | }); 563 | 564 | describe('Discriminators', () => { 565 | const data = { 566 | _type: 'text', 567 | text: 'Feathers!!!' 568 | }; 569 | 570 | afterEach(async () => { 571 | await posts.remove(null, { query: {} }); 572 | }); 573 | 574 | it('can get a discriminated model', async () => { 575 | const result = await posts.create(data); 576 | const post = await posts.get(result._id); 577 | 578 | expect(post._type).to.equal('text'); 579 | expect(post.text).to.equal('Feathers!!!'); 580 | }); 581 | 582 | it('can find discriminated models by the type', async () => { 583 | await posts.create(data); 584 | 585 | const result = await posts.find({ query: { _type: 'text' } }); 586 | 587 | result.forEach(element => { 588 | expect(element._type).to.equal('text'); 589 | }); 590 | }); 591 | 592 | it('can create a discriminated model', async () => { 593 | const result = await posts.create(data); 594 | 595 | expect(result._type).to.equal('text'); 596 | expect(result.text).to.equal('Feathers!!!'); 597 | }); 598 | 599 | it('can update a discriminated model', async () => { 600 | const update = { 601 | _type: 'text', 602 | text: 'Hello, world!', 603 | createdAt: Date.now(), 604 | updatedAt: Date.now() 605 | }; 606 | const params = { 607 | query: { 608 | _type: 'text' 609 | } 610 | }; 611 | 612 | const post = await posts.create(data); 613 | const result = await posts.update(post._id, update, params); 614 | 615 | expect(result._type).to.equal('text'); 616 | expect(result.text).to.equal('Hello, world!'); 617 | }); 618 | 619 | it('can patch a discriminated model', async () => { 620 | const update = { 621 | text: 'Howdy folks!' 622 | }; 623 | const params = { 624 | query: { 625 | _type: 'text' 626 | } 627 | }; 628 | const post = await posts.create(data); 629 | const result = await posts.patch(post._id, update, params); 630 | 631 | expect(result.text).to.equal('Howdy folks!'); 632 | }); 633 | 634 | it('can remove a discriminated model', async () => { 635 | const post = await posts.create(data); 636 | const result = await posts.remove(post._id, { query: { _type: 'text' } }); 637 | 638 | expect(result._type).to.equal('text'); 639 | }); 640 | }); 641 | 642 | describe('Query Modifiers', () => { 643 | beforeEach(async () => { 644 | const rufus = await QMPets.create({ type: 'dog', name: 'Rufus' }); 645 | const margeaux = await QMPets.create({ type: 'cat', name: 'Margeaux' }); 646 | const bob = await QMPets.create({ type: 'cat', name: 'Bob' }); 647 | 648 | _petIds.Rufus = rufus._id; 649 | _petIds.Margeaux = margeaux._id; 650 | _petIds.Bob = bob._id; 651 | }); 652 | 653 | afterEach(async () => { 654 | await QMPets.remove(null, { query: {} }); 655 | }); 656 | 657 | it('can apply a global query modifier with find', async () => { 658 | const params = { 659 | query: {} 660 | }; 661 | 662 | const data = await QMPets.find(params); 663 | 664 | expect(data.length).to.equal(2); 665 | expect(data[0].type).to.equal('cat'); 666 | expect(data[1].type).to.equal('cat'); 667 | }); 668 | 669 | it('can apply a global query modifier with get', async () => { 670 | const margeaux = await QMPets.get(_petIds.Margeaux); 671 | 672 | expect(margeaux.name).to.equal('Margeaux'); 673 | 674 | try { 675 | await QMPets.get(_petIds.Rufus); 676 | throw new Error('Should never get here'); 677 | } catch (error) { 678 | expect(error.name).to.equal('NotFound'); 679 | } 680 | }); 681 | 682 | it('can apply a local query modifier with find', async () => { 683 | const params = { 684 | query: {}, 685 | queryModifier: (query) => { 686 | query.where('type', 'dog'); 687 | } 688 | }; 689 | 690 | const data = await QMPets.find(params); 691 | 692 | expect(data.length).to.equal(1); 693 | expect(data[0].type).to.equal('dog'); 694 | }); 695 | 696 | it('can apply a local query modifier with get', async () => { 697 | const params = { 698 | query: {}, 699 | queryModifier: (query) => { 700 | query.where('type', 'dog'); 701 | } 702 | }; 703 | 704 | const result = await QMPets.get(_petIds.Rufus, params); 705 | 706 | expect(result.name).to.equal('Rufus'); 707 | 708 | try { 709 | await QMPets.get(_petIds.Margeaux, params); 710 | throw new Error('Should never get here'); 711 | } catch (error) { 712 | expect(error.name).to.equal('NotFound'); 713 | } 714 | }); 715 | 716 | it('can disable the global query modifier', async () => { 717 | const params = { 718 | query: {}, 719 | queryModifier: false 720 | }; 721 | 722 | const data = await QMPets.find(params); 723 | 724 | expect(data.length).to.equal(3); 725 | }); 726 | }); 727 | 728 | testSuite(app, errors, 'peeps', '_id'); 729 | testSuite(app, errors, 'peeps-customid', 'customid'); 730 | }); 731 | -------------------------------------------------------------------------------- /test/models/candidate.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | 4 | const CandidateSchema = new Schema({ 5 | name: { type: String, required: false, unique: true }, 6 | token_id: { type: String, required: false, unique: false } 7 | }); 8 | 9 | const CandidateModel = mongoose.model('Candidate', CandidateSchema); 10 | 11 | module.exports = CandidateModel; 12 | -------------------------------------------------------------------------------- /test/models/customer.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | 4 | const CustomerSchema = new Schema({ 5 | name: { type: String, required: false, unique: false } 6 | }); 7 | 8 | const CustomerModel = mongoose.model('Customer', CustomerSchema); 9 | 10 | module.exports = CustomerModel; 11 | -------------------------------------------------------------------------------- /test/models/index.js: -------------------------------------------------------------------------------- 1 | const Pet = require('./pet'); 2 | const User = require('./user'); 3 | const Peeps = require('./peeps'); 4 | const CustomPeeps = require('./peeps-customid'); 5 | const Post = require('./post'); 6 | const TextPost = require('./text-post'); 7 | const Token = require('./token'); 8 | const Candidate = require('./candidate'); 9 | const Customer = require('./customer'); 10 | 11 | module.exports = { 12 | Pet, 13 | User, 14 | Peeps, 15 | CustomPeeps, 16 | Post, 17 | TextPost, 18 | Token, 19 | Candidate, 20 | Customer 21 | }; 22 | -------------------------------------------------------------------------------- /test/models/peeps-customid.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | 4 | const PeepsSchema = new Schema({ 5 | customid: { 6 | type: Schema.Types.ObjectId, 7 | default: function () { 8 | return new mongoose.Types.ObjectId(); 9 | } 10 | }, 11 | name: { type: String, required: true }, 12 | age: { type: Number }, 13 | created: { type: Boolean, default: false }, 14 | time: { type: Number } 15 | }); 16 | 17 | module.exports = mongoose.model('PeepsCustomid', PeepsSchema); 18 | -------------------------------------------------------------------------------- /test/models/peeps.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | 4 | const PeepsSchema = new Schema({ 5 | name: { type: String, required: true }, 6 | age: { type: Number }, 7 | created: { type: Boolean, default: false }, 8 | time: { type: Number } 9 | }); 10 | 11 | module.exports = mongoose.model('Peeps', PeepsSchema); 12 | -------------------------------------------------------------------------------- /test/models/pet.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | 4 | const PetSchema = new Schema({ 5 | type: { type: String, required: true }, 6 | name: { type: String, required: true, unique: true }, 7 | gender: { type: String, required: false, select: false } 8 | }); 9 | 10 | const PetModel = mongoose.model('Pet', PetSchema); 11 | 12 | module.exports = PetModel; 13 | -------------------------------------------------------------------------------- /test/models/post.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | 4 | const options = { 5 | discriminatorKey: '_type' 6 | }; 7 | 8 | const PostSchema = new Schema({ 9 | createdAt: { type: Date, default: Date.now }, 10 | updatedAt: { type: Date, default: Date.now } 11 | }, options); 12 | 13 | const PostModel = mongoose.model('Post', PostSchema); 14 | 15 | module.exports = PostModel; 16 | -------------------------------------------------------------------------------- /test/models/text-post.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | const Post = require('./post'); 4 | 5 | const options = { 6 | discriminatorKey: '_type' 7 | }; 8 | 9 | const TextPostSchema = new Schema({ 10 | text: { type: String, default: null } 11 | }, options); 12 | 13 | const TextPostModel = Post.discriminator('text', TextPostSchema); 14 | 15 | module.exports = TextPostModel; 16 | -------------------------------------------------------------------------------- /test/models/todo.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | 4 | const TodoSchema = new Schema({ 5 | text: { type: String, required: true }, 6 | complete: { type: Boolean, default: false }, 7 | createdAt: { type: Date, default: Date.now }, 8 | updatedAt: { type: Date, default: Date.now } 9 | }); 10 | 11 | const TodoModel = mongoose.model('Todo', TodoSchema); 12 | 13 | module.exports = TodoModel; 14 | -------------------------------------------------------------------------------- /test/models/token.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | 4 | const TokenSchema = new Schema({ 5 | token: { type: String, required: true, unique: false } 6 | }); 7 | 8 | const TokenModel = mongoose.model('Token', TokenSchema); 9 | 10 | module.exports = TokenModel; 11 | -------------------------------------------------------------------------------- /test/models/user.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | 4 | const negativeAgeValidator = function () { 5 | // With option "context: 'query'", mongoose pass a Query object to validators when update or findAndModify a mongoose object 6 | // Plus findAndModify mongoose method put document in a $set object when update mongoose method don't 7 | // So you're forced to test these cases to retrieve your properties 8 | const age = (this.constructor.name === 'Query' ? (this.getUpdate().$set ? this.getUpdate().$set.age : this.getUpdate().age) : this.age); 9 | return (age > 0); 10 | }; 11 | 12 | const UserSchema = new Schema({ 13 | name: { type: String, required: true }, 14 | age: { 15 | type: Number, 16 | validate: [negativeAgeValidator, 'Age couldn\'t be negative'] 17 | }, 18 | created: { type: Boolean, default: false }, 19 | time: { type: Number }, 20 | pets: [{ type: Schema.ObjectId, ref: 'Pet' }] 21 | }); 22 | 23 | const UserModel = mongoose.model('User', UserSchema); 24 | 25 | module.exports = UserModel; 26 | -------------------------------------------------------------------------------- /test/transaction-manager.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai'); 2 | const feathers = require('@feathersjs/feathers'); 3 | const transactionManager = require('../lib/transaction-manager'); 4 | const adapter = require('../lib'); 5 | const mongoose = require('mongoose'); 6 | 7 | const { 8 | Candidate, 9 | Token, 10 | Customer 11 | } = require('./models'); 12 | const app = feathers() 13 | .use('/candidates', adapter({ Model: Candidate })) 14 | .use('/tokens', adapter({ Model: Token })) 15 | .use('/customers', adapter({ Model: Customer })); 16 | 17 | const candidate = app.service('candidates'); 18 | const token = app.service('tokens'); 19 | const customerService = app.service('customers'); 20 | 21 | const saveCandidateToken = async context => { 22 | const newToken = context.data.token; 23 | const tokenResult = await token.create({ token: newToken }, context.params); 24 | context.data.token_id = tokenResult._id; 25 | return context; 26 | }; 27 | 28 | candidate.hooks({ 29 | before: { 30 | create: [transactionManager.beginTransaction, saveCandidateToken], 31 | remove: [transactionManager.beginTransaction] 32 | }, 33 | after: { 34 | create: [transactionManager.commitTransaction], 35 | remove: [transactionManager.commitTransaction] 36 | }, 37 | error: { 38 | create: [transactionManager.rollbackTransaction], 39 | remove: [transactionManager.rollbackTransaction] 40 | } 41 | }); 42 | 43 | token.hooks({ 44 | before: { 45 | remove: [transactionManager.beginTransaction] 46 | }, 47 | after: { 48 | remove: [transactionManager.rollbackTransaction] 49 | } 50 | }); 51 | 52 | describe('transaction-manager', () => { 53 | const newCandidate = { name: 'abcd', token: '123' }; 54 | it('Create transaction and commit session', async () => { 55 | await Candidate.deleteMany(); 56 | await Token.deleteMany(); 57 | return candidate.create(newCandidate).then(result => { 58 | expect(result.name).to.equal(newCandidate.name); 59 | }); 60 | }); 61 | it('Create transaction and rollback session', async () => { 62 | newCandidate.token = '456'; 63 | return candidate.create(newCandidate).then().catch(error => { 64 | expect(error.name).to.equal('Conflict'); 65 | return token.find({ query: { token: '456' } }).then(result => { 66 | expect(result).to.have.lengthOf(0); 67 | }); 68 | }); 69 | }); 70 | }); 71 | 72 | // Start a transaction in a mongoose session. 73 | const getTransaction = async () => { 74 | const client = mongoose.connections[0]; 75 | const session = await client.startSession(); 76 | await session.startTransaction(); 77 | const params = {}; 78 | params.mongoose = { session }; 79 | return params; 80 | }; 81 | 82 | describe('transaction-manager for find and get', () => { 83 | const data = { name: 'Customer' }; 84 | it('Create with transaction and find without transaction', async () => { 85 | const params = await getTransaction(); 86 | await customerService.create(data, params); 87 | const customers = await customerService.find(); 88 | await params.mongoose.session.commitTransaction(); 89 | await Customer.deleteMany(); 90 | expect(0).to.equal(customers.length); 91 | }); 92 | 93 | it('Create and find with transaction', async () => { 94 | const params = await getTransaction(); 95 | await customerService.create(data, params); 96 | const customers = await customerService.find(params); 97 | await params.mongoose.session.commitTransaction(); 98 | await Customer.deleteMany(); 99 | expect(1).to.equal(customers.length); 100 | }); 101 | 102 | it('Create with transaction and get without transaction', async () => { 103 | const params = await getTransaction(); 104 | try { 105 | const newCustomer = await customerService.create(data, params); 106 | await customerService.get(newCustomer._id); 107 | } catch (error) { 108 | expect('not-found').to.equal(error.className); 109 | } finally { 110 | await params.mongoose.session.abortTransaction(); 111 | } 112 | }); 113 | 114 | it('Create and get with transaction', async () => { 115 | const params = await getTransaction(); 116 | const newCustomer = await customerService.create(data, params); 117 | const customer = await customerService.get(newCustomer._id, params); 118 | await params.mongoose.session.commitTransaction(); 119 | await Customer.deleteMany(); 120 | expect(newCustomer.name).to.equal(customer.name); 121 | }); 122 | }); 123 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | // TypeScript Version: 4.0 2 | import { Params, Paginated, Id, NullableId, Hook } from '@feathersjs/feathers'; 3 | import { AdapterService, ServiceOptions, InternalServiceMethods } from '@feathersjs/adapter-commons'; 4 | import { Model, Document, Query } from 'mongoose'; 5 | 6 | export namespace hooks { 7 | function toObject(options?: any, dataField?: string): Hook; 8 | } 9 | 10 | export namespace transactionManager { 11 | const beginTransaction: Hook; 12 | const commitTransaction: Hook; 13 | const rollbackTransaction: Hook; 14 | } 15 | 16 | export interface MongooseServiceOptions extends ServiceOptions { 17 | Model: Model; 18 | lean: boolean; 19 | overwrite: boolean; 20 | useEstimatedDocumentCount: boolean; 21 | queryModifier?: (query: Query, params: Params) => void; 22 | queryModifierKey?: string; 23 | } 24 | 25 | export class Service extends AdapterService implements InternalServiceMethods { 26 | Model: Model; 27 | options: MongooseServiceOptions; 28 | 29 | constructor(config?: Partial); 30 | 31 | _find(params?: Params): Promise>; 32 | _get(id: Id, params?: Params): Promise; 33 | _create(data: Partial | Array>, params?: Params): Promise; 34 | _update(id: NullableId, data: T, params?: Params): Promise; 35 | _patch(id: NullableId, data: Partial, params?: Params): Promise; 36 | _remove(id: NullableId, params?: Params): Promise; 37 | } 38 | 39 | declare const mongoose: ((config?: Partial) => Service); 40 | export default mongoose; 41 | -------------------------------------------------------------------------------- /types/index.test.ts: -------------------------------------------------------------------------------- 1 | import { Schema, model } from 'mongoose'; 2 | import feathers from '@feathersjs/feathers'; 3 | import { default as mongoose, hooks, transactionManager } from 'feathers-mongoose'; 4 | 5 | const MessageSchema = new Schema({ 6 | text: { 7 | type: String, 8 | required: true 9 | } 10 | }); 11 | const Model = model('Message', MessageSchema); 12 | const service = mongoose({ 13 | Model, 14 | lean: true 15 | }); 16 | 17 | service.Model; 18 | 19 | const app = feathers().use('/test', service); 20 | 21 | app.service('test').hooks({ 22 | after: { 23 | all: [ 24 | hooks.toObject(), 25 | transactionManager.beginTransaction, 26 | transactionManager.commitTransaction, 27 | transactionManager.rollbackTransaction 28 | ] 29 | } 30 | }); 31 | -------------------------------------------------------------------------------- /types/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "lib": ["es6"], 5 | "target": "es6", 6 | "noImplicitAny": true, 7 | "noImplicitThis": true, 8 | "strictNullChecks": true, 9 | "strictFunctionTypes": true, 10 | "noEmit": true, 11 | "skipLibCheck": true, 12 | // If the library is an external module (uses `export`), this allows your test file to import "mylib" instead of "./index". 13 | // If the library is global (cannot be imported via `import` or `require`), leave this out. 14 | "baseUrl": ".", 15 | "paths": { "feathers-mongoose": ["."] } 16 | } 17 | } -------------------------------------------------------------------------------- /types/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "dtslint/dtslint.json", // Or "dtslint/dt.json" if on DefinitelyTyped 3 | "rules": { 4 | "indent": [true, "spaces"] 5 | } 6 | } --------------------------------------------------------------------------------