Skip to content

Commit

Permalink
Merge 4a7b41e into a9144f4
Browse files Browse the repository at this point in the history
  • Loading branch information
Nataniel López committed Sep 12, 2019
2 parents a9144f4 + 4a7b41e commit 4a9eae6
Show file tree
Hide file tree
Showing 8 changed files with 259 additions and 39 deletions.
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,14 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Added
- `config-validator` module
- `MongoDBConfigError` module
- config validations
- indexes and uniqueIndexes docs

## [1.3.3] - 2019-08-29
## Fixed
### Fixed
- Now `insert` returns ID of the object inserted, as it was expected
- Now `save` returns ID of the object if it was inserted / Unique Index used as filter if it was updated, as it was expected

Expand Down
90 changes: 69 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,42 +11,81 @@ npm install --save @janiscommerce/mongodb

## API

- `new MongoDB({config})`
### `new MongoDB({config})`
Constructs the MongoDB driver instance, connected with the `config [Object]`.

Config usage:
**Config validations:**

- host `[String]` (optional): MongoDB host, default: `localhost`
- protocol `[String]` (optional): host protocol, default: `mongodb://`
- port `[Number]` (optional): host port, default: `27017`
- user `[String]` (optional): host username, default none
- password `[String]` (optional): host user password, default none
- database `[String]` **(required)**: MongoDB database
- limit `[Number]` (optional): Limit for `get`/`getTotals` operations, default: `500`

**Config usage:**
```js
{
protocol: 'mongodb://', // Default "mongodb://"
host: 'localhost', // Default "localhost"
port: 27017, // Default 27017
limit: 500, // Default 500
protocol: 'mongodb://',
host: 'localhost',
port: 27017,
limit: 500,
user: 'fizzmod',
password: 'sarasa',
database: 'myDB'
}
```

- ***async*** `createIndexes(model)`
Creates indexes and unique indexes from the model to the MongoDB database.
Requires a `model [Model]`
### ***async*** `createIndexes(model)`
Creates indexes and unique indexes from the model to the MongoDB database.
Requires a `model [Model]`
**Important:** This method must be executed before any operation with new databases. If not, the unique indexes will not have any effect in your database.

**Indexes and unique indexes in Model:**
In order to avoid errors you must to specify your indexes and unique indexes in the Model:
- indexes `[Array]`: The indexes of your model, also you can add an `[Array]` for combine indexes. See example below.
- uniqueIndexes `[Array]`: The unique indexes of your model, also you can add an `[Array]` for combine unique indexes. See example below.

**Combined indexes are used for getting filters with multiple indexes due they are combined as one**

**Model example**
```js
class MyModel extends Model {

static get uniqueIndexes() {
return [
'myUniqueIndex',
['my', 'combined','unique','indexes']
];
}

- ***async*** `insert(model, {item})`
static get indexes() {
return [
'myIndex',
['my', 'combined', 'indexes']
];
}

}
```

### ***async*** `insert(model, {item})`
Insert a item into the database.
Requires a `model [Model]` and `item [Object]`.
Returns `String` *ID* of the item inserted or rejects if cannot.

- ***async*** `multiInsert(model, [{items}])`
### ***async*** `multiInsert(model, [{items}])`
Inserts multiple items into the database.
Requires a `model [Model]` and `item [Object array]`.
Returns `true` if the operation was successfull or `false` if not.

- ***async*** `update(model, {values}, {filter})`
### ***async*** `update(model, {values}, {filter})`
Updates one or multiple items from the database.
Requires a `model [Model]`, `values [Object]` and `filter [Object]` (MongoDB filter).
Returns the modified/updated elements count.

- ***async*** `get(model, {parameters})`
### ***async*** `get(model, {parameters})`
Search elements from the database then returns an `[Array]` with the results `[Object]`.
Requires a `model [Model]`, `parameters [Object]` are optional.

Expand All @@ -73,7 +112,7 @@ Parameters example:
}
```

- ***async*** `getTotals(model)`
### ***async*** `getTotals(model)`
Get the totals of the items from the latest get operation with pagination.
Requires a `model [Model]`
Returns an `[Object]` with the total count, page size, pages and selected page.
Expand All @@ -88,31 +127,31 @@ getTotals return example:
}
```

- ***async*** `save(model, {item})`
### ***async*** `save(model, {item})`
Insert/update a item into the database.
Requires a `model [Model]` and `item [Object]`.
Returns `String` **ID** of the item *inserted* or **Unique Index** used as filter if it was *updated* or rejects if cannot.

- ***async*** `multiSave(model, [{items}], limit)`
### ***async*** `multiSave(model, [{items}], limit)`
Insert/update multiple items into the database.
Requires a `model [Model]` and `items [Object array]`.
`limit [Number]` (optional, default 1000): Specifies the max amount of items that can be written at same time.
Returns `true/false` if the result was successfully or not.

- ***async*** `remove(model, {item})`
### ***async*** `remove(model, {item})`
Removes the specified item from the database.
Requires a `model [Model]` and `item [Object]`.
Returns `true/false` if the result was successfully or not.

- ***async*** `multiRemove(model, {filter})`
### ***async*** `multiRemove(model, {filter})`
Removes multiple items from the database.
Requires a `model [Model]` and `filter [Object]` (MongoDB filter).
Returns `deletedCount [Number]`.

## Errors

The errors are informed with a `MongoDBError`.
This object has a code that can be useful for a correct error handling.
This object has a code that can be useful for a correct error handling.
The codes are the following:

| Code | Description |
Expand All @@ -121,8 +160,17 @@ The codes are the following:
| 2 | Empty unique indexes |
| 3 | Invalid or empty model |
| 4 | Internal mongodb error |
| 5 | Invalid config |
| 6 | Invalid item |
| 5 | Invalid item |

The config validation errors are informed with a `MongoDBConfigError`
This object has a code that can be useful for a correct error handling.
The codes are the following:

| Code | Description |
|------|--------------------------------|
| 1 | Invalid config |
| 2 | Invalid setting |
| 3 | Required setting |

## Usage

Expand Down
69 changes: 69 additions & 0 deletions lib/config-validator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
'use strict';

const MongoDBConfigError = require('./mongodb-config-error');

const MONGODB_CONFIG_STRUCT = {
protocol: {
type: 'string'
},
host: {
type: 'string'
},
port: {
type: 'number'
},
user: {
type: 'string'
},
password: {
type: 'string'
},
database: {
type: 'string',
required: true
},
limit: {
type: 'number'
}
};

/**
* @class ConfigValidator
* @classdesc Validates config struct
*/
class ConfigValidator {

/**
* Validate the received config struct
* @throws if the struct is invalid
*/
static validate(config) {

if(!config || typeof config !== 'object' || Array.isArray(config))
throw new MongoDBConfigError('Invalid config: Should be an object.', MongoDBConfigError.codes.INVALID_CONFIG);

for(const [setting, terms] of Object.entries(MONGODB_CONFIG_STRUCT)) {

switch(typeof config[setting]) {

case terms.type:
break;

case 'undefined':
if(terms.required)
throw new MongoDBConfigError(`Invalid config: '${setting}' is required.`, MongoDBConfigError.codes.REQUIRED_SETTING);
break;

default:
throw new MongoDBConfigError(`Invalid setting '${setting}': Expected ${terms.type} but received ${typeof config[setting]}.`,
MongoDBConfigError.codes.INVALID_SETTING);

}

}

}

}

module.exports = ConfigValidator;
4 changes: 3 additions & 1 deletion lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

const MongoDB = require('./mongodb');
const MongoDBError = require('./mongodb-error');
const MongoDBConfigError = require('./mongodb-config-error');

module.exports = {
MongoDB,
MongoDBError
MongoDBError,
MongoDBConfigError
};
23 changes: 23 additions & 0 deletions lib/mongodb-config-error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use strict';

class MongoDBConfigError extends Error {

static get codes() {

return {
INVALID_CONFIG: 1,
INVALID_SETTING: 2,
REQUIRED_SETTING: 3
};

}

constructor(err, code) {
super(err);
this.message = err.message || err;
this.code = code;
this.name = 'MongoDBConfigError';
}
}

module.exports = MongoDBConfigError;
9 changes: 6 additions & 3 deletions lib/mongodb.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ const logger = require('@janiscommerce/logger');

const MongoDBError = require('./mongodb-error');

const ConfigValidator = require('./config-validator');

const DEFAULT_LIMIT = 500;

const MONGODB_DEFAULT_PROTOCOL = 'mongodb://';
Expand All @@ -21,18 +23,19 @@ const MONGODB_DEFAULT_HOST = 'localhost';
class MongoDB {

constructor(config) {
if(!config || typeof config !== 'object' || Array.isArray(config))
throw new MongoDBError('Invalid config', MongoDBError.codes.INVALID_CONFIG);

ConfigValidator.validate(config);

this.config = {
protocol: config.protocol || MONGODB_DEFAULT_PROTOCOL,
host: config.host || MONGODB_DEFAULT_HOST,
port: config.port || 27017,
user: config.user || '',
password: config.password || '',
database: config.database || '',
database: config.database,
limit: config.limit || DEFAULT_LIMIT
};

// Avoid protocol duplication
this.config.host = this.config.host.replace(this.config.protocol, '');
}
Expand Down
75 changes: 75 additions & 0 deletions tests/config-validator-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
'use strict';

const assert = require('assert');

const ConfigValidator = require('./../lib/config-validator');

const { MongoDBConfigError } = require('./../lib');

describe('ConfigValidator', () => {

describe('validate()', () => {

it('should throw invalid config when the config is empty', () => {

assert.throws(() => ConfigValidator.validate(), {
name: 'MongoDBConfigError',
code: MongoDBConfigError.codes.INVALID_CONFIG
});

});


it('should throw invalid config when the config is not an object', () => {

assert.throws(() => ConfigValidator.validate('string'), {
name: 'MongoDBConfigError',
code: MongoDBConfigError.codes.INVALID_CONFIG
});

});


it('should throw invalid config when the config is an array', () => {

assert.throws(() => ConfigValidator.validate([]), {
name: 'MongoDBConfigError',
code: MongoDBConfigError.codes.INVALID_CONFIG
});

});

it('should throw required setting when a required setting is missing', () => {

assert.throws(() => ConfigValidator.validate({}), {
name: 'MongoDBConfigError',
code: MongoDBConfigError.codes.REQUIRED_SETTING
});

});


['string', ['array']].forEach(type => {

it('should throw invalid setting when a setting has an unexpected type', () => {

assert.throws(() => ConfigValidator.validate({
database: 'myDB',
port: type
}), {
name: 'MongoDBConfigError',
code: MongoDBConfigError.codes.INVALID_SETTING
});
});

});

it('should not throw when the settings are correct', () => {

assert.doesNotThrow(() => ConfigValidator.validate({
database: 'myDB',
port: 27017
}));
});
});
});
Loading

0 comments on commit 4a9eae6

Please sign in to comment.