A Promise-Based DocumentDB ODM Client for NodeJS
npm install doqmentdb##DoQmentDB - A Promise-Based DocumentDB Client
[![NPM version][npm-image]][npm-url]
[![Build status][travis-image]][travis-url]
[![Test coverage][coveralls-image]][coveralls-url]
[![Dependency Status][david-image]][david-url]
[![License][license-image]][license-url]
[![Downloads][downloads-image]][downloads-url]
> DoQmentDB is a tiny layer that provides the simplicity of MongoDB for DocumentDB users(support schema, hooks/middleware, atomic-transactions, udf and more).
##Table of contents:
- [![Gitter][gitter-image]][gitter-url]
- Get Started
- Examples
- Changelog
- Database
- create
- insert
- getDatabase
- find
- findById
- findOrCreate
- remove
- use
- Collection
- create
- insert
- createOrUpdate
- upsert
- getCollection
- find
- findOne
- findById
- findAndRemove
- findOneAndRemove
- findAndModify
- findOneAndModify
- findOrCreate
- update
- updateOne
- Queries
- Operations
- Schema
- Atomic Transactions
- Middleware
- pre
- post
#Get Started
(1) You can install DoQmentDB using 2 different methods:
- clone & build this repository
- via npm: by running $ npm install doqmentdb from your terminal
(2) Add to your project:
``js`
var DoqmentDB = require('doqmentdb');`
(3) Start Playing with DoqmentDB:jstest
var DoQmentDB = require('doqmentdb');
// Create DocumentDB connection
var connection = new (require('documentdb').DocumentClient)(HOST, OPTIONS);
// Pass connection and database-name, if is not exist it will create one.users
var db = new DoQmentDB(connection, 'test');
// Create a CollectionManager instance, if is not exist it will create one.users
var users = db.use('users');
// Using schema
users.schema(model);
// Add hooks(see full example)
users.pre('save', function(next) {
var doc = this;
doc.createdAt = new Date().toString();
next();
});
// Each http function returns a Promise with two specific methods: success and error.
users.create({ name: '6534' })
.then(console.log);
users.findById(1)
.then(console.log);
users.findAndRemove({ isAdmin: false })
.then(console.log);
`connection
#Database
Create a DatabaseManager by passing and databaseName.`jstest
var DoQmentDB = require('doqmentdb');
// Create DocumentDB connection
var connection = new (require('documentdb').DocumentClient)(HOST, OPTIONS);
// if is not exist it will create one`
var db = new DoQmentDB(connection, 'test');db.create(string)
##create
Get name and crete new collection in the used db.
Usage: insert
Aliases: Object
Returns: `js`
db.create('users')
.then(console.log);db.getDatabase()
##getDatabase
Return the used database.
Usage: `js`
db.getDatabase()
.then(console.log);db.find(object[optional])
##find
find collection by given object params.
Note: to return all documents, omit params argument or pass an empty object({}).
Usage: Array
Returns: `js
db.find()
.then(console.log); // Return all collections
db.find({ id: 'users' })
.then(console.log); // Return collections where id equal to users`string
##findById
find collection by given id. db.findById(string)
Usage: Object
Returns: `js`
db.findById('users')
.then(console.log);db.findOrCreate(object)
##findOrCreate
get object properties, search for collection, if it not exist create one.
Usage: Object
Returns: `js`
db.findOrCreate({ name: 'users', id: '#1' })
.then(console.log);String
##remove
get collection id as a , if it exist - remove it and return undefined, else return false. db.remove(string)
Usage: undefined
Returns: or Boolean`js`
db.remove('test')
.then(console.log);CollectionManager
##use
get collection name and return instance. collection
Note: if the given is not exist it will create one. var coll = db.use(string);
Usage: object
Returns: instanceof CollectionManager`js`
var users = db.use('users'); // This operation is not async.use
#Collection
Create a CollectionManager by passing to function a collection name.`js`
var users = db.use('users');
console.log(users.constructor.name); // Collectionusers.create(object)
##create
get object properties, and create new document under the used collection.
Usage: insert
Aliases: Object
Returns: `js`
users.create({ name: 'Ariel', admin: true })
.then(console.log); // { name: 'Ariel', admin: true, id: '8...31', _self: ... }users.createOrUpdate(object)
##createOrUpdate
create a new document under the current collection, or update an existing document with the same id.
Usage: upsert
Aliases: Object
Returns: `js`
users.upsert({ id: 'my_user_id', admin: true })
.then(console.log); // { id: 'my_user_id', admin: true, _self: ... }users.getCollection()
##getCollection
return the used collection.
Usage: `js`
users.getCollection()
.then(console.log);users.find(object)
##find
get object properties and return array of results.
Usage: Array
Note: to return all collections, omit params argument or pass an empty object({}).
Returns: `js`
users.find({ active: true })
.then(console.log);users.findOne(object)
##findOne
get object properties and return the first matching result.
Usage: Object
Returns: `js`
users.findOne({ active: true, name: 'Bar' })
.then(console.log);string
##findById
find document by giving a id. users.findById(string)
Usage: Object
Returns: `js`
users.findById('53...3')
.then(console.log);users.findAndRemove(object)
##findAndRemove
get object properties to search, find the equivalents and remove them.
Usage: Array
Returns: $
Note: if you want support atomic-transactions(i.e: do things concurrently, e.g: distributed system),
you need use this method prefix with sign.`js
users.findAndRemove({ name: 'Ariel' })
.then(console.log);
// Using stored procedure
users.$findAndRemove({ name: 'Ariel' })
.then(console.log);
// Remove all users
users.findAndRemove({})
.then(console.log);
`users.findOneAndRemove(object)
##findOneAndRemove
get object properties, and remove the first matching result.
Usage: undefined
Returns: or Boolean $
Note: if you want support atomic-transactions(i.e: do things concurrently, e.g: distributed system),
you need use this method prefix with sign.`js
users.findOneAndRemove({ name: 'Ariel', admin: true })
.then(console.log);
// Using stored procedure
users.$findOneAndRemove({ name: 'Ariel', admin: true })
.then(console.log);
`extend
##findAndModify
get object properties to search, find the equivalents and modify them( operation). users.findAndModify(object, extend)
Usage: update
Aliases: Array
Returns: $
Note: if you want support atomic-transactions(i.e: do things concurrently, e.g: distributed system),
you need use this method prefix with sign.`js
users.update({ name: 'Ariel', admin: true }, { admin: false })
.then(console.log);
// Push 'a' and 'b' to list field(do it concurrently)`
['a', 'b'].forEach(function(ch) {
users.$update({}, { list: { $push: ch } });
});extend
##findOneAndModify
get object properties and modify( operation) the first matching. users.findOneAndModify(object, extend)
Usage: updateOne
Aliases: Object
Returns: $
Note: if you want support atomic-transactions(i.e: do things concurrently, e.g: distributed system),
you need use this method prefix with sign.`js
users.findOneAndModify({ admin: false }, { admin: true })
.then(console.log);
// Using stored procedure
users.$findOneAndModify({ admin: false }, { admin: true })
.then(console.log);
`users.findOrCreate(object)
##findOrCreate
get object properties, search for document, if it not exist create one.
Usage: Object
Returns: $
Note: if you want support atomic-transactions(i.e: do things concurrently, e.g: distributed system),
you need use this method prefix with sign.`js
users.findOrCreate({ admin: false, name: 'Ariel' })
.then(console.log);
// Using stored procedure
users.$findOrCreate({ admin: false, name: 'Ariel' })
.then(console.log);
`
#Queries
###Operators
* Logical & Conjunctive:
* $or OR$and
* AND$not
* NOT$nor
* NOT(... OR ...)$gt
* Comparison:
* >$gte
* >=$lt
* <$lte
* <=$ne
* <> or !=$in
* UDF:
* like Array.prototype.some(...)$all
* like Array.prototype.every(...)$type
* typeof value$regex
* new RegExp(...).test(value)$size
* test array.length
###Examples
`js
users.find({ a: 1, b: 2, c: '3' })
// ... r WHERE r.a=1 AND r.b=2 AND r.c="3"
users.find({ $or: [{ a: 2, b: 3}, { c: 3 }] })
// ... r WHERE ((r.a=2 AND r.b=3) OR r.c=3)
users.find({ $not: { a: 1, b: 2, c: 3 } })
// ... r WHERE NOT(r.a=1 AND r.b=2 AND r.c=3)
users.find({ $nor: [ { a: 1 }, { b: 3 }]})
// ... r WHERE NOT(r.a=1 OR r.b=3)
users.find({ $nor: [ { a: 1, b: 1 }, { c: 3 } ] })
// ... r WHERE NOT((r.a=1 AND r.b=1) OR r.c=3)
users.find({ $not: { name: { $gt: 3 }, age: 12 } })
// ... r WHERE NOT(r.name > 3 AND r.age=12)
users.find({ $not: { name: { $ne: 'bar' } } })
// ... r WHERE NOT(r.name <> "bar")
users.find({ $or: [
{ name: { $ne: 'Ariel' } },
{ age: { $lte: 26 } },
{ $and: [
{ isAdmin: { $ne: false } },
{ isUser: { $ne: false } }
]}
]})
// ... r WHERE r.name <> "Ariel" OR r.age <= 26 OR (r.isAdmin <> false AND r.isUser <> false)
users.find({ coins: { $in: 2 } })
// ... r WHERE inUDF(r.coins, 2)
users.find({ $not: { age: { $type: 'number' } } })
// ... r WHERE NOT(typeUDF(r.age, "number"))
`
#Operations
When using one of the update operations(e.g: .update(), .findAndModify(), etc...), you could use the build-in prototype functions(based on the type) prefixing with $ sign. users.update({ ... }, { keyName: { $method: value } })
Usage: `
Note: value could be single or array of arguments.jsarr
// Find all, and push 2 to field
users.update({}, { arr: { $push: 2 } });
// Suffix all users name with #
users.update({}, { name: { $concat: '#' } });
// Trim the name from foo to o`
users.update({ name: 'foo' }, { name: { $substr: [1,1] } });
#Schema
Manage your documents with schema.
fields:
* typeString
required*
* used for type comparing, (e.g: , Boolean, Number, etc..).default
* regex
optional*
* value fallback
* /^[a-zA-Z0-9@:%_\+.~#?&//=|/d]{10,}$/
optional*
* regex validation, (e.g: email validation - ).error
* regex
optional*
* return message to fields that fail in the validation phase(/type). see: exampleexpose
* expose
optional*
* by default is true, unless you set it to false, it's means that all the find operations returns the documents without exposing this fields. see: example
Example using schema:
schema: model.js`js
module.exports = {
/**
* @field name
* @default no default value
*/
name: {
type: String,
'default': ''
},
/**
* @field email
* @default no default value
* @regex email, min-length = 10
*/
email: {
type: String,
'default': '',
regex: /^[a-zA-Z0-9@:%_\+.~#?&//=|/d]{10,}$/,
error: 'email must be type string, valid email address, and least 10 chars',
expose: true
},
/**
* @field password
* @default no default value
* @regex password
*/
password: {
type: String,
'default': '',
regex: /^.(?=.{8,})(?=.[a-zA-Z])(?=.\d)(?=.[!#$%&? "]).*$/,
error: 'password must be type string, contain 8 chars and at least one number, ' +
'one letter and one unique character such as !#$%&? "',
expose: false
},
/**
* @field isAdmin
* @default false
*/
isAdmin: {
type: Boolean,
'default': false
}
};
`model.js
using schema()`js
var DoQmentDB = require('doqmentdb');
var model = require('./model'); // Get model/schema
var connection = new (require('documentdb') // Create DocumentDB connection
.DocumentClient)(CONFIG.HOST, CONFIG.OPTIONS);
var db = new DoQmentDB(connection, CONFIG.DB); // Create DBManager 'test'
var users = db.use('users'); // Create CollectionManager 'users'
users.schema(model); // Using schema
users.create({ password: 'Ar2!as_s'})
.then(console.log)
.catch(console.log);
/*
[Error:
email must be type string, valid email address, and least 10 chars
]
*/
users.create({ name: 'Ariel', email: 'ariel.com', password: 'Ar2!as_s'})
.then(console.log)
.catch(console.log);
/*
[Error:
email must be type string, valid email address, and least 10 chars
]
*/
users.create({ name: 'Ariel', email: 'a8m@gm.com', password: 'Ar2!as_s'})
.then(console.log)
.catch(console.log);
/*
{
name: 'Ariel',
email: 'a8m@gm.com',
password: 'Ar2!as_s',
id: '2eb7...c0',
...
}
*/
users.find({})
.then(console.log);
/*
Get all documents but without exposing fields(i.e: omit password field)`
*/
see: How to architect your models
#Middleware
Middleware/Hooks are executed at the document level(create/save/insert, update, remove/delete).
There are two types of middleware, pre and post.
##pre
Usage: users.pre(operation, callback) pre
Note: middleware are executed one after another, when each middleware calls next. `
Example:js
users.pre('save', function(next) {
var doc = this;
doc.createdAt = new Date().toString();
next();
}, function(next) {
var doc = this;
doc.updatedAt = new Date().toString();
next();
});
// Do something async
users.pre('save', function(next) {
var doc = this;
bcrypt.genSalt(10, function(err, salt) {
bcrypt.hash(doc.password, salt, function(err, hash) {
doc.password = hash;
next();
});
});
});
// ##Note: the order is importatnt, this example order:
// createdAt(), updatedAT(), hash/bcrypt(), and then the .create operation will called`users.post(operation, callback)
##post
Usage: post
Note: middleware are executed in parallel. `
Example:js`
users.post('save', function(doc) {
logger(new Date(), doc, 'saved!')
});$
#Atomic Transactions
Since v0.2.6 DoQmentDB supports atomic-transactions using a built-in sporcs(i.e: stored procedures) to handle concurrently well.
Note: To perform some operation this way, you should prefix it with . `
Read More: DocumentDB - Atomic Transactionsjsconsuming
// Lets take some example of from two differentsmodel
// Service-Bus queues and update the same /document
//
// Note: This also could happen in a distributed system, when two operations happens in parallel
// We have a stores collection that holds the sales and the usersstore
// fields per (a Document)atomic
// We are using the version of update, because we don't want to lose data
sbs.receiveQueueMessage('sales', function(msg) {
stores.$update({ id: msg.id }, { sales: { $push: msg.sale } });
// Polling again...
});
sbs.receiveQueueMessage('users', function(msg) {
stores.$update({ id: msg.id }, { users: { $push: msg.user } });
// ...
});
`
#Examples
* Koa DocumentDB Example - A users CRUD application using Koa and DocumentDB.
* Express DocumentDB Example - Express application using DocumentDB.
#Changelog
##0.2.9
* Schema Fix- issue #26
##0.2.8
* Add aliases: updateOne and $updateOne(the conccurent one)findAndModify
* refactor the built-in stored procedure()
##0.2.6
Since 0.2.6 DoQmentDB support atomic transactions using DocumentDB stored procedures. update
Methods that support:
* /findAndModifyfindOneAndModify
* findOrCreate
* findAndRemove
* findOneAndRemove
*
If you want to use one of this methods, you should use them prefix with $ sign.`jslist
// Push 'a' and 'b' to field(do it concurrently)``
['a', 'b'].forEach(function(ch) {
users.$update({}, { list: { $push: ch } });
});
[npm-image]: https://img.shields.io/npm/v/doqmentdb.svg?style=flat-square
[npm-url]: https://npmjs.org/package/doqmentdb
[travis-image]: https://img.shields.io/travis/a8m/doqmentdb.svg?style=flat-square
[travis-url]: https://travis-ci.org/a8m/doqmentdb
[coveralls-image]: https://img.shields.io/coveralls/a8m/doqmentdb.svg?style=flat-square
[coveralls-url]: https://coveralls.io/r/a8m/doqmentdb
[david-image]: http://img.shields.io/david/a8m/doqmentdb.svg?style=flat-square
[david-url]: https://david-dm.org/a8m/doqmentdb
[license-image]: http://img.shields.io/npm/l/doqmentdb.svg?style=flat-square
[license-url]: LICENSE
[downloads-image]: http://img.shields.io/npm/dm/doqmentdb.svg?style=flat-square
[downloads-url]: https://npmjs.org/package/doqmentdb
[gitter-image]: https://badges.gitter.im/Join%20Chat.svg
[gitter-url]: https://gitter.im/a8m/doqmentdb?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge