Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
32 changed files
with
10,151 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
{ | ||
"parserOptions": { | ||
"ecmaVersion": 2017 | ||
}, | ||
"env": { | ||
"amd": true, | ||
"es6": true, | ||
"node": true, | ||
"mocha": true | ||
}, | ||
"extends": [ | ||
"eslint:recommended", | ||
"google" | ||
], | ||
"rules": { | ||
"max-len": [ | ||
"error", | ||
100, | ||
2, | ||
{ | ||
"ignoreComments": true, | ||
"ignoreUrls": true, | ||
"ignorePattern": "^\\s*var\\s.+=\\s*(require\\s*\\()|(/)" | ||
} | ||
], | ||
"indent": [ | ||
"error", | ||
2 | ||
], | ||
"space-before-function-paren": [ | ||
"error", | ||
"always" | ||
], | ||
"one-var": [ | ||
"error", | ||
{ | ||
"initialized": "never", | ||
"uninitialized": "always" | ||
} | ||
], | ||
"valid-jsdoc": [ | ||
"error", | ||
{ | ||
"requireReturnDescription": false, | ||
"prefer": { | ||
"returns": "returns" | ||
} | ||
} | ||
], | ||
"capitalized-comments": "error", | ||
"padded-blocks": "off", | ||
"new-cap": "off" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
.DS_Store | ||
node_modules | ||
*.sock | ||
.idea | ||
.nyc_output | ||
coverage | ||
.coveralls.yml |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"all": true, | ||
"check-coverage": true, | ||
"cache": false, | ||
"lines": 100, | ||
"statements": 100, | ||
"functions": 100, | ||
"branches": 100 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
language: node_js | ||
node_js: | ||
- node | ||
- 10 | ||
- 8 | ||
- 6 | ||
services: | ||
- mongodb | ||
script: | ||
- npm run test-with-coverage | ||
after_success: | ||
- npm run publish-coverage |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2019 ALIA Technologies | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,250 @@ | ||
# Loopback Aggregate mixin for MongoDB | ||
|
||
[![Build Status](https://travis-ci.org/aliatech/loopback-mongo-aggregate-mixin.svg?branch=master)](https://travis-ci.org/aliatech/loopback-mongo-aggregate-mixin) | ||
[![Coverage Status](https://coveralls.io/repos/github/aliatech/loopback-mongo-aggregate-mixin/badge.svg?branch=master)](https://coveralls.io/github/aliatech/loopback-mongo-aggregate-mixin?branch=master) | ||
[![npm version](https://badge.fury.io/js/%40aliatech%2Floopback-mongo-aggregate-mixin.svg)](https://badge.fury.io/js/%40aliatech%2Floopback-mongo-aggregate-mixin) | ||
|
||
Give models the ability to query native **MongoDB aggregates** and **build instances** from results. | ||
|
||
* Accepts both Loopback filter's features and pipeline stages, it will merge in a single parsed pipeline to aggregate. | ||
* Accepts relations' fields within the root where, it will be handled as $lookup stages. | ||
* Refactor the logic from Loopback which is responsible for building the model instances and take advantage of it. | ||
|
||
This Loopback mixin is intended to be used together with MongoDB connector. | ||
Works for Loopback 2 and 3. | ||
|
||
## How to install | ||
|
||
Install the package through NPM | ||
|
||
```bash | ||
npm i -S @aliatech/loopback-mongo-aggregate-mixin | ||
``` | ||
|
||
Install the package through Yarn | ||
|
||
```bash | ||
yarn add --prod @aliatech/loopback-mongo-distinct-mixin | ||
``` | ||
|
||
## Basic configuration | ||
|
||
Include the mixin in `server/model-config.json`. Example for Loopback 3: | ||
|
||
```json | ||
{ | ||
"_meta": { | ||
"sources": [ | ||
"loopback/common/models", | ||
"loopback/server/models", | ||
"../common/models", | ||
"./models" | ||
], | ||
"mixins": [ | ||
"loopback/common/mixins", | ||
"../node_modules/loopback-mongo-aggregate-mixin/lib", | ||
"../common/mixins" | ||
] | ||
} | ||
} | ||
``` | ||
|
||
Enable the mixin in your model definition, ie `person.json`. | ||
|
||
```json | ||
{ | ||
"name": "Person", | ||
"properties": { | ||
"name": "string" | ||
}, | ||
"mixins": { | ||
"Aggregate": true | ||
} | ||
} | ||
``` | ||
|
||
## Usage | ||
|
||
Invoke `aggregate` method passing either: | ||
|
||
* A regular Loopback filter (where, fields, include, order, skip, limit) | ||
* An aggregate pipeline | ||
* A combination of both | ||
|
||
### Basic example | ||
|
||
Find a random sample of 3 persons born after 1980: | ||
|
||
```js | ||
app.models.Person.aggregate({ | ||
where: {birthDate: {gt: new Date('1980')}}, | ||
aggregate: [{$sample: {size: 3}}], | ||
}, (err, persons) => { | ||
if (err) return next(err); | ||
// persons are Person model instances | ||
}); | ||
``` | ||
|
||
### Find where relation properties | ||
|
||
Relation properties can be specified in the "where" criteria using dot notation. | ||
$lookup stages will be automatically generated to reach those relations and filter the root documents by such criteria. | ||
it works like a "LEFT JOIN" feature, however it's still necessary to add the "include" filter | ||
if you require the relation to be hydrated. | ||
|
||
Example: Bring persons who are part of a team in which there is some person who is born after 2001 | ||
|
||
```js | ||
app.models.Person.aggregate({ | ||
where: {'team.persons.birthDate': {$gt: new Date('2001')}}, | ||
}, (err, persons) => { | ||
if (err) return next(err); | ||
// persons are Person model instances | ||
}); | ||
``` | ||
|
||
Note: It works for hasOne, belongsTo and hasMany. Filtering by embedded properties is not affected and continues to work as usual. | ||
|
||
### Build instances on demand | ||
|
||
The aggregate result often needs some processing before building the model instances. | ||
It's possible to postpone the build phase until the models' data are resolved. | ||
|
||
Example: Bring the persons count together with a specific page | ||
|
||
```js | ||
Person.aggregate([{ | ||
group: { | ||
_id: null, | ||
total: {$sum: 1}, | ||
objects: {$push: '$$ROOT'}, | ||
}, | ||
}, { | ||
project: { | ||
total: 1, | ||
items: {$slice: ['$objects', pageStart, pageLength]}, | ||
}, | ||
}], {build: false}, (err, data, build) => { | ||
if (err) return next(err); | ||
// data is a plan structure {total, items} where items is a array of plain objects, not model instances. | ||
build(data.items, (err, persons) => { | ||
if (err) return next(err); | ||
// now you got persons as Peron model instances | ||
}); | ||
}); | ||
``` | ||
|
||
Note: Pipeline array can be directly passed as argument. Also stage names can obviate "$" character. | ||
|
||
Few more things worth commenting here: | ||
|
||
* In this case, model documents are not brought as root result, | ||
so we need to disable the automatic build through an options object `{build: false}`. | ||
* When automatic build is disabled, we'll be provided with a build function as a third argument of the callback. | ||
Person instances are finally obtained calling build function with `data.items`. | ||
* Build on demand feature will be always available as a model static method `Model.buildResult`. | ||
|
||
### GeoNear example | ||
|
||
Combine regular "where" with $geoNear stage. | ||
$geoNear will be moved to the pipeline head as MongoDB requires. | ||
|
||
```js | ||
app.models.Company.aggregate({ | ||
where: {sector: 'Software'}, | ||
aggregate: [{ | ||
$geoNear: { | ||
near: {type: 'Point', coordinates: [-0.076132, 51.508530]}, | ||
distanceField: 'distance', | ||
maxDistance: 5000, // 5Km. | ||
spherical: true, | ||
}, | ||
}], | ||
}, (err, companies) => { | ||
if (err) return done(err); | ||
// companies are Company model instances | ||
}); | ||
``` | ||
|
||
## Advanced configuration | ||
|
||
Enable the mixin passing an options object instead of just true. | ||
|
||
**Available options:** | ||
|
||
| Option | Type | Required | Description | | ||
| --------------------- | ----------| --------- | ----------------- | | ||
| mongodbArgs | object | optional | Set defaults for MongoDB aggregate command options (default `{}`). Check the [official documentation](https://docs.mongodb.com/manual/reference/command/aggregate/ "Link to documentation") | | ||
| build | boolean | optional | Whether to automatically build model instances from aggregate results by default. (default `true`) | | ||
| buildOptions | object | optional | Set defaults for building process options (default `{notify: true}`) | | ||
| buildOptions.notify | boolean | optional | Whether to notify model operation hooks on build by default (default `true`) | | ||
|
||
Any of these options can be replaced on the fly with the following syntax: | ||
|
||
```js | ||
app.models.Person(filter, options, callback); | ||
``` | ||
|
||
The `options` argument will be timely merged with the defaults for a single call. | ||
|
||
### Example: Allow MongoDB to use disk | ||
|
||
This is a MongoDB aggregate command option that prevent memory issues on large queries. | ||
It can be enabled by default as follows: | ||
|
||
```json | ||
{ | ||
"name": "Person", | ||
"properties": { | ||
"name": "string" | ||
}, | ||
"mixins": { | ||
"Aggregate": { | ||
"mongodbArgs": { | ||
"allowDiskUse": true | ||
} | ||
} | ||
} | ||
} | ||
``` | ||
|
||
Or just enable the option on the fly for a single call: | ||
|
||
```js | ||
app.models.Person(filter, {mongodbArgs: {allowDiskUse: true}}, callback); | ||
``` | ||
|
||
# Debug | ||
|
||
Prepend DEBUG environment when running server or tests to display what pipelines are being sent to MongoDB: | ||
|
||
```bash | ||
DEBUG=loopback:mixins:aggregate node . # Run server with debug | ||
``` | ||
|
||
# Testing | ||
|
||
Install develop dependences | ||
|
||
````bash | ||
npm i -D # If you use NPM | ||
yarn install # If you use Yarn | ||
```` | ||
|
||
Execute tests | ||
|
||
````bash | ||
npm test # Without coverage check | ||
npm run test-with-coverage # With coverage check | ||
```` | ||
|
||
# Credits | ||
|
||
Inspired by [https://github.com/BoLaMN/loopback-mongo-aggregate-mixin](https://github.com/BoLaMN/loopback-mongo-aggregate-mixin "Github's repository") | ||
|
||
Developed by | ||
[Juan Costa](https://github.com/Akeri "Github's profile") | ||
for | ||
[ALIA Technologies](https://github.com/aliatech "Github's profile") | ||
|
||
<img src="http://alialabs.com/images/logos/logo-full-big.png" title="ALIA Technologies" alt="ALIA Technologies" height=100/> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
'use strict'; | ||
|
||
const aggregate = require('./lib/aggregate'); | ||
const rewriteId = require('./lib/rewrite-id'); | ||
|
||
module.exports = function (app) { | ||
app.loopback.modelBuilder.mixins.define('Aggregate', aggregate); | ||
}; | ||
|
||
module.exports.rewriteId = rewriteId; |
Oops, something went wrong.