Skip to content

Commit

Permalink
Merge branch 'release/1.0.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
Akeri committed Feb 21, 2019
2 parents d558187 + d78572c commit 4a5188d
Show file tree
Hide file tree
Showing 32 changed files with 10,151 additions and 0 deletions.
54 changes: 54 additions & 0 deletions .eslintrc.json
@@ -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"
}
}
7 changes: 7 additions & 0 deletions .gitignore
@@ -0,0 +1,7 @@
.DS_Store
node_modules
*.sock
.idea
.nyc_output
coverage
.coveralls.yml
9 changes: 9 additions & 0 deletions .nycrc.json
@@ -0,0 +1,9 @@
{
"all": true,
"check-coverage": true,
"cache": false,
"lines": 100,
"statements": 100,
"functions": 100,
"branches": 100
}
12 changes: 12 additions & 0 deletions .travis.yml
@@ -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
21 changes: 21 additions & 0 deletions LICENSE
@@ -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.
250 changes: 250 additions & 0 deletions README.md
@@ -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/>
10 changes: 10 additions & 0 deletions index.js
@@ -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;

0 comments on commit 4a5188d

Please sign in to comment.