From 4c8e3d28732a7240c28dd8aaee51e1a89d8f12c5 Mon Sep 17 00:00:00 2001 From: Jason Dobry Date: Mon, 15 Aug 2016 23:42:32 -0700 Subject: [PATCH] Initial commit. --- .github/CONTRIBUTING.md | 37 + .github/ISSUE_TEMPLATE.md | 5 + .github/PULL_REQUEST_TEMPLATE.md | 6 + .gitignore | 37 + .mailmap | 1 + AUTHORS | 8 + CHANGELOG.md | 3 + CONTRIBUTORS | 8 + LICENSE | 21 + README.md | 44 ++ circle.yml | 14 + conf.json | 26 + dist/js-data-documentdb.d.ts | 40 + mocha.start.js | 51 ++ package.json | 73 ++ rollup.config.js | 19 + src/index.js | 1207 ++++++++++++++++++++++++++++++ typings.json | 8 + 18 files changed, 1608 insertions(+) create mode 100644 .github/CONTRIBUTING.md create mode 100644 .github/ISSUE_TEMPLATE.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .gitignore create mode 100644 .mailmap create mode 100644 AUTHORS create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTORS create mode 100644 LICENSE create mode 100644 README.md create mode 100644 circle.yml create mode 100644 conf.json create mode 100644 dist/js-data-documentdb.d.ts create mode 100644 mocha.start.js create mode 100644 package.json create mode 100644 rollup.config.js create mode 100644 src/index.js create mode 100644 typings.json diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..0129662 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,37 @@ +# Contributing to js-data-documentdb + +[Read the general Contributing Guide](http://js-data.io/docs/contributing). + +## Project structure + +* `dist/` - Contains final build files for distribution +* `doc/` - Output folder for JSDocs +* `src/` - Project source code +* `test/` - Project tests + +## Clone, build & test + +1. `clone git@github.com:js-data/js-data-documentdb.git` +1. `cd js-data-documentdb` +1. `npm install` +1. `npm run build` - Lint and build distribution files +1. `npm run mocha` - Run tests (must set `DOCUMENT_DB_ENDPOINT` and `DOCUMENT_DB_KEY` environment variables) + +## To cut a release + +1. Checkout master +1. Bump version in `package.json` appropriately +1. Update `CHANGELOG.md` appropriately +1. Run `npm run release` +1. Commit and push changes +1. Checkout `release`, merge `master` into `release` +1. Run `npm run release` again +1. Commit and push changes +1. Make a GitHub release + - tag from `release` branch + - set tag name to version + - set release name to version + - set release body to changelog entry for the version +1. `npm publish .` + +See also [Community & Support](http://js-data.io/docs/community). diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..b66d610 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,5 @@ +(delete this line) Find out how to get help here: http://js-data.io/docs/community. + + + +Thanks! diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..4a12cfd --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,6 @@ +Fixes # (it's a good idea to open an issue first for discussion) + +- [ ] - `npm test` succeeds +- [ ] - Code coverage does not decrease (if any source code was changed) +- [ ] - Appropriate JSDoc comments were updated in source code (if applicable) +- [ ] - Approprate changes to js-data.io docs have been suggested ("Suggest Edits" button) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..61d2ecc --- /dev/null +++ b/.gitignore @@ -0,0 +1,37 @@ +dist/*.js +dist/*.map +# Logs +logs +*.log + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directory +# Commenting this out is preferred by some people, see +# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- +node_modules/ +bower_components/ + +# Users Environment Variables +.lock-wscript + +.idea/ +*.iml + +doc/ +.nyc_output/ \ No newline at end of file diff --git a/.mailmap b/.mailmap new file mode 100644 index 0000000..21ac730 --- /dev/null +++ b/.mailmap @@ -0,0 +1 @@ +Jason Dobry Jason Dobry diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..75e3ad1 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,8 @@ +# This is the official list of js-data-documentdb project authors. +# +# Names are formatted as: +# Name or Organization +# +# The email address is not required for organizations. +# +Jason Dobry diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f5f3f56 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +##### 0.1.0 - 15 August 2016 + +- Initial Release diff --git a/CONTRIBUTORS b/CONTRIBUTORS new file mode 100644 index 0000000..f83b45c --- /dev/null +++ b/CONTRIBUTORS @@ -0,0 +1,8 @@ +# This is the official list of js-data-documentdb project contributors. +# +# Names are formatted as: +# Name +# +InternalFX +Jason Dobry +Ollie Relph diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..660bfb4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014-2016 js-data-documentdb project authors + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2ad2e23 --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +js-data logo + +# js-data-documentdb + +[![Slack Status][sl_b]][sl_l] +[![npm version][npm_b]][npm_l] +[![Circle CI][circle_b]][circle_l] +[![npm downloads][dn_b]][dn_l] +[![Coverage Status][cov_b]][cov_l] + +Azure DocumentDB adapter for [js-data](http://www.js-data.io/). + +To get started, visit __[http://js-data.io](http://www.js-data.io/docs/js-data-documentdb)__. + +## Links + +* [Quick start](http://www.js-data.io/docs/home#quick-start) - Get started in 5 minutes +* [Guides and Tutorials](http://www.js-data.io/docs/home) - Learn how to use JSData +* [`DocumentDBAdapter` Guide](http://www.js-data.io/docs/js-data-documentdb) - Learn how to use `DocumentDBAdapter` +* [API Reference Docs](http://api.js-data.io) - Explore components, methods, options, etc. +* [Community & Support](http://js-data.io/docs/community) - Find solutions and chat with the community +* [General Contributing Guide](http://js-data.io/docs/contributing) - Give back and move the project forward + * [Contributing to js-data-documentdb](https://github.com/js-data/js-data-documentdb/blob/master/.github/CONTRIBUTING.md) + +## License + +The MIT License (MIT) + +Copyright (c) 2014-2016 js-data-documentdb project authors + +* [LICENSE](https://github.com/js-data/js-data-documentdb/blob/master/LICENSE) +* [AUTHORS](https://github.com/js-data/js-data-documentdb/blob/master/AUTHORS) +* [CONTRIBUTORS](https://github.com/js-data/js-data-documentdb/blob/master/CONTRIBUTORS) + +[sl_b]: http://slack.js-data.io/badge.svg +[sl_l]: http://slack.js-data.io +[npm_b]: https://img.shields.io/npm/v/js-data-documentdb.svg?style=flat +[npm_l]: https://www.npmjs.org/package/js-data-documentdb +[circle_b]: https://img.shields.io/circleci/project/js-data/js-data-documentdb.svg?style=flat +[circle_l]: https://circleci.com/gh/js-data/js-data-documentdb +[dn_b]: https://img.shields.io/npm/dm/js-data-documentdb.svg?style=flat +[dn_l]: https://www.npmjs.org/package/js-data-documentdb +[cov_b]: https://img.shields.io/codecov/c/github/js-data/js-data-documentdb.svg?style=flat +[cov_l]: https://codecov.io/github/js-data/js-data-documentdb diff --git a/circle.yml b/circle.yml new file mode 100644 index 0000000..8a4610d --- /dev/null +++ b/circle.yml @@ -0,0 +1,14 @@ +general: + branches: + ignore: + - gh-pages +machine: + node: + version: 5.7.0 +dependencies: + pre: + - npm i -g npm codecov nyc + - npm i js-data@^3.0.0-rc.4 documentdb +test: + post: + - nyc report --reporter=lcov | codecov diff --git a/conf.json b/conf.json new file mode 100644 index 0000000..b1ae28c --- /dev/null +++ b/conf.json @@ -0,0 +1,26 @@ + { + "source": { + "includePattern": ".*js$" + }, + "plugins": ["plugins/markdown"], + "opts": { + "template": "./node_modules/ink-docstrap/template", + "destination": "./doc/", + "recurse": true, + "verbose": true, + "readme": "./README.md", + "package": "./package.json" + }, + "templates": { + "theme": "jsdata", + "systemName": "js-data-documentdb", + "copyright": "js-data-documentdb Copyright © 2014-2016 js-data-documentdb project authors", + "outputSourceFiles": true, + "linenums": true, + "footer": "", + "analytics": { + "ua": "UA-55528236-2", + "domain": "api.js-data.io" + } + } +} \ No newline at end of file diff --git a/dist/js-data-documentdb.d.ts b/dist/js-data-documentdb.d.ts new file mode 100644 index 0000000..1ff9c35 --- /dev/null +++ b/dist/js-data-documentdb.d.ts @@ -0,0 +1,40 @@ +import {Adapter} from 'js-data-adapter' + +interface IDict { + [key: string]: any; +} +interface IBaseAdapter extends IDict { + debug?: boolean, + raw?: boolean +} +interface IBaseDocumentDBAdapter extends IBaseAdapter { + documentOpts?: any +} +export class DocumentDBAdapter extends Adapter { + static extend(instanceProps?: IDict, classProps?: IDict): typeof DocumentDBAdapter + constructor(opts?: IBaseDocumentDBAdapter) +} +export interface OPERATORS { + '==': Function + '===': Function + '!=': Function + '!==': Function + '>': Function + '>=': Function + '<': Function + '<=': Function + 'isectEmpty': Function + 'isectNotEmpty': Function + 'in': Function + 'notIn': Function + 'contains': Function + 'notContains': Function +} +export interface version { + full: string + minor: string + major: string + patch: string + alpha: string | boolean + beta: string | boolean +} \ No newline at end of file diff --git a/mocha.start.js b/mocha.start.js new file mode 100644 index 0000000..93c11c5 --- /dev/null +++ b/mocha.start.js @@ -0,0 +1,51 @@ +/*global assert:true */ +'use strict' + +// prepare environment for js-data-adapter-tests +import 'babel-polyfill' + +import * as JSData from 'js-data' +import JSDataAdapterTests from './node_modules/js-data-adapter/dist/js-data-adapter-tests' +import * as JSDataDocumentDB from './src/index' + +const assert = global.assert = JSDataAdapterTests.assert +global.sinon = JSDataAdapterTests.sinon + +JSDataAdapterTests.init({ + debug: false, + JSData: JSData, + Adapter: JSDataDocumentDB.DocumentDBAdapter, + adapterConfig: { + documentOpts: { + urlConnection: process.env.DOCUMENT_DB_ENDPOINT, + auth: { + masterKey: process.env.DOCUMENT_DB_KEY + } + } + }, + methods: [ + 'create', + 'createMany', + 'count', + 'destroy', + 'destroyAll', + 'find', + 'findAll', + 'sum' + ], + // js-data-documentdb does NOT support these features + // xfeatures: [ + // 'findAllLikeOp', + // 'filterOnRelations' + // ] + features: [] +}) + +describe('exports', function () { + it('should have correct exports', function () { + assert(JSDataDocumentDB.DocumentDBAdapter) + assert(JSDataDocumentDB.OPERATORS) + assert(JSDataDocumentDB.OPERATORS['==']) + assert(JSDataDocumentDB.version) + }) +}) diff --git a/package.json b/package.json new file mode 100644 index 0000000..5bfa6ce --- /dev/null +++ b/package.json @@ -0,0 +1,73 @@ +{ + "name": "js-data-documentdb", + "description": "DocumentDB adapter for js-data.", + "version": "0.1.0", + "homepage": "https://github.com/js-data/js-data-documentdb", + "repository": { + "type": "git", + "url": "https://github.com/js-data/js-data-documentdb.git" + }, + "author": "js-data-documentdb project authors", + "license": "MIT", + "main": "./dist/js-data-documentdb.js", + "typings": "./dist/js-data-documentdb.d.ts", + "files": [ + "dist/", + "src/", + "AUTHORS", + "CONTRIBUTORS" + ], + "keywords": [ + "data", + "datastore", + "store", + "database", + "adapter", + "documentdb", + "azure" + ], + "standard": { + "parser": "babel-eslint", + "globals": [ + "describe", + "it", + "sinon", + "assert", + "before", + "after", + "beforeEach", + "afterEach" + ], + "ignore": [ + "dist/", + "doc/" + ] + }, + "babel": { + "presets": [ + "es2015" + ] + }, + "scripts": { + "lint": "repo-tools lint \"**/*.js\"", + "bundle": "rollup -c rollup.config.js -f cjs -o dist/js-data-documentdb.js -m dist/js-data-documentdb.js.map src/index.js && repo-tools write-version dist/js-data-documentdb.js", + "doc": "jsdoc -c conf.json src node_modules/js-data-adapter/src", + "watch": "watch \"npm run bundle\" src/", + "build": "npm run lint && npm run bundle", + "mocha": "mocha -t 20000 -R dot -r babel-core/register -r babel-polyfill mocha.start.js", + "cover": "nyc --require babel-core/register --require babel-polyfill --cache mocha -t 20000 -R dot mocha.start.js && nyc report --reporter=html", + "test": "npm run build && npm run cover", + "release": "npm test && npm run doc && repo-tools updates && repo-tools changelog && repo-tools authors" + }, + "dependencies": { + "js-data-adapter": "~0.8.2", + "mout": "1.0.0" + }, + "peerDependencies": { + "js-data": "^3.0.0-rc.4", + "documentdb": "1.x.x" + }, + "devDependencies": { + "js-data-repo-tools": "0.5.6" + } +} diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 0000000..acced09 --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,19 @@ +var babel = require('rollup-plugin-babel') + +module.exports = { + external: [ + 'documentdb', + 'js-data', + 'js-data-adapter', + 'mout/string/underscore' + ], + plugins: [ + babel({ + babelrc: false, + presets: [ + 'es2015-rollup' + ], + exclude: 'node_modules/**' + }) + ] +} diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..899e2bb --- /dev/null +++ b/src/index.js @@ -0,0 +1,1207 @@ +import {utils} from 'js-data' +import { + Adapter, + reserved +} from 'js-data-adapter' +import {DocumentClient} from 'documentdb' +import underscore from 'mout/string/underscore' + +const R_OPTS_DEFAULTS = { + db: 'test' +} +const INSERT_OPTS_DEFAULTS = {} +const UPDATE_OPTS_DEFAULTS = {} +const DELETE_OPTS_DEFAULTS = {} +const RUN_OPTS_DEFAULTS = {} + +const checkIfNameExists = function (name, parameters) { + let exists = false + parameters.forEach(function (parameter) { + if (parameter.name === name) { + exists = true + return false + } + }) + return exists +} + +const addParameter = function (field, value, parameters) { + const name = `@${field}` + let newName = name + let count = 1 + + while (checkIfNameExists(newName, parameters)) { + newName = name + count + count++ + } + parameters.push({ + name: newName, + value + }) + return newName +} + +const equal = function (field, value, parameters, collectionId) { + return `${collectionId}.${field} = ${addParameter(field, value, parameters)}` +} + +const notEqual = function (field, value, parameters, collectionId) { + return `${collectionId}.${field} != ${addParameter(field, value, parameters)}` +} + +/** + * Default predicate functions for the filtering operators. + * + * @name module:js-data-documentdb.OPERATORS + * @property {Function} = Equality operator. + * @property {Function} == Equality operator. + * @property {Function} != Inequality operator. + * @property {Function} > "Greater than" operator. + * @property {Function} >= "Greater than or equal to" operator. + * @property {Function} < "Less than" operator. + * @property {Function} <= "Less than or equal to" operator. + * @property {Function} isectEmpty Operator to test that the intersection + * between two arrays is empty. + * @property {Function} isectNotEmpty Operator to test that the intersection + * between two arrays is NOT empty. + * @property {Function} in Operator to test whether a value is found in the + * provided array. + * @property {Function} notIn Operator to test whether a value is NOT found in + * the provided array. + * @property {Function} contains Operator to test whether an array contains the + * provided value. + * @property {Function} notContains Operator to test whether an array does NOT + * contain the provided value. + */ +export const OPERATORS = { + '=': equal, + '==': equal, + '===': equal, + '!=': notEqual, + '!==': notEqual, + '>': function (field, value, parameters, collectionId) { + return `${collectionId}.${field} > ${addParameter(field, value, parameters)}` + }, + '>=': function (field, value, parameters, collectionId) { + return `${collectionId}.${field} >= ${addParameter(field, value, parameters)}` + }, + '<': function (field, value, parameters, collectionId) { + return `${collectionId}.${field} < ${addParameter(field, value, parameters)}` + }, + '<=': function (field, value, parameters, collectionId) { + return `${collectionId}.${field} <= ${addParameter(field, value, parameters)}` + }, + 'in': function (field, value, parameters, collectionId) { + return `${collectionId}.${field} IN ${addParameter(field, value, parameters)}` + }, + 'notIn': function (field, value, parameters, collectionId) { + return `${collectionId}.${field} NOT IN ${addParameter(field, value, parameters)}` + } +} + +Object.freeze(OPERATORS) + +/** + * DocumentDBAdapter class. + * + * @example + * // Use Container instead of DataStore on the server + * import {Container} from 'js-data' + * import {DocumentDBAdapter} from 'js-data-documentdb' + * + * // Create a store to hold your Mappers + * const store = new Container() + * + * // Create an instance of DocumentDBAdapter with default settings + * const adapter = new DocumentDBAdapter() + * + * // Mappers in "store" will use the DocumentDB adapter by default + * store.registerAdapter('documentdb', adapter, { default: true }) + * + * // Create a Mapper that maps to a "user" table + * store.defineMapper('user') + * + * @class DocumentDBAdapter + * @extends Adapter + * @param {Object} [opts] Configuration options. + * @param {boolean} [opts.debug=false] See {@link Adapter#debug}. + * @param {Object} [opts.deleteOpts={}] See {@link DocumentDBAdapter#deleteOpts}. + * @param {Object} [opts.insertOpts={}] See {@link DocumentDBAdapter#insertOpts}. + * @param {Object} [opts.operators={@link module:js-data-documentdb.OPERATORS}] See {@link DocumentDBAdapter#operators}. + * @param {Object} [opts.r] See {@link DocumentDBAdapter#r}. + * @param {boolean} [opts.raw=false] See {@link Adapter#raw}. + * @param {Object} [opts.documentOpts={}] See {@link DocumentDBAdapter#documentOpts}. + * @param {Object} [opts.runOpts={}] See {@link DocumentDBAdapter#runOpts}. + * @param {Object} [opts.updateOpts={}] See {@link DocumentDBAdapter#updateOpts}. + */ +export function DocumentDBAdapter (opts) { + utils.classCallCheck(this, DocumentDBAdapter) + opts || (opts = {}) + + // Setup non-enumerable properties + Object.defineProperties(this, { + /** + * The DocumentDB client used by this adapter. Use this directly when you + * need to write custom queries. + * + * @example Use default instance. + * import {DocumentDBAdapter} from 'js-data-documentdb' + * const adapter = new DocumentDBAdapter() + * adapter.client.createDatabase('foo', function (err, db) {...}) + * + * @example Configure default instance. + * import {DocumentDBAdapter} from 'js-data-documentdb' + * const adapter = new DocumentDBAdapter({ + * documentOpts: { + * urlConnection: 'your-endpoint', + * auth: { + * masterKey: '1asdfa8s0dfa9sdf98' + * } + * } + * }) + * adapter.client.createDatabase('foo', function (err, db) {...}) + * + * @example Provide a custom instance. + * import {DocumentClient} from 'documentdb' + * import {DocumentDBAdapter} from 'js-data-documentdb' + * const client = new DocumentClient(...) + * const adapter = new DocumentDBAdapter({ + * client: client + * }) + * adapter.client.createDatabase('foo', function (err, db) {...}) + * + * @name DocumentDBAdapter#r + * @type {Object} + */ + client: { + writable: true, + value: undefined + }, + databases: { + value: {} + }, + indices: { + value: {} + }, + collections: { + value: {} + } + }) + + Adapter.call(this, opts) + + /** + * Default options to pass to r#insert. + * + * @name DocumentDBAdapter#insertOpts + * @type {Object} + * @default {} + */ + this.insertOpts || (this.insertOpts = {}) + utils.fillIn(this.insertOpts, INSERT_OPTS_DEFAULTS) + + /** + * Default options to pass to r#update. + * + * @name DocumentDBAdapter#updateOpts + * @type {Object} + * @default {} + */ + this.updateOpts || (this.updateOpts = {}) + utils.fillIn(this.updateOpts, UPDATE_OPTS_DEFAULTS) + + /** + * Default options to pass to r#delete. + * + * @name DocumentDBAdapter#deleteOpts + * @type {Object} + * @default {} + */ + this.deleteOpts || (this.deleteOpts = {}) + utils.fillIn(this.deleteOpts, DELETE_OPTS_DEFAULTS) + + /** + * Default options to pass to r#run. + * + * @name DocumentDBAdapter#runOpts + * @type {Object} + * @default {} + */ + this.runOpts || (this.runOpts = {}) + utils.fillIn(this.runOpts, RUN_OPTS_DEFAULTS) + + /** + * Override the default predicate functions for the specified operators. + * + * @name DocumentDBAdapter#operators + * @type {Object} + * @default {} + */ + this.operators || (this.operators = {}) + utils.fillIn(this.operators, OPERATORS) + + /** + * Options to pass to a new `DocumentClient` instance, if one was not provided + * at {@link DocumentDBAdapter#client}. See the [DocumentClient API][readme] + * for instance options. + * + * [readme]: http://azure.github.io/azure-documentdb-node/DocumentClient.html + * + * @name DocumentDBAdapter#documentOpts + * @see http://azure.github.io/azure-documentdb-node/DocumentClient.html + * @type {Object} + * @property {string} urlConnection The service endpoint to use to create the + * client. + * @property {object} auth An object that is used for authenticating requests + * and must contains one of the auth options. + * @property {object} auth.masterkey The authorization master key to use to + * create the client. + * Keys for the object are resource Ids and values are the resource tokens. + * @property {object[]} auth.resourceTokens An object that contains resources tokens. + * Keys for the object are resource Ids and values are the resource tokens. + * @property {string} auth.permissionFeed An array of Permission objects. + * @property {string} [connectionPolicy] An instance of ConnectionPolicy class. + * This parameter is optional and the default connectionPolicy will be used if + * omitted. + * @property {string} [consistencyLevel] An optional parameter that represents + * the consistency level. It can take any value from ConsistencyLevel. + */ + this.documentOpts || (this.documentOpts = {}) + utils.fillIn(this.documentOpts, R_OPTS_DEFAULTS) + + if (!this.client) { + this.client = new DocumentClient( + this.documentOpts.urlConnection, + this.documentOpts.auth, + this.documentOpts.connectionPolicy, + this.documentOpts.consistencyLevel + ) + } +} + +Adapter.extend({ + constructor: DocumentDBAdapter, + + _count (mapper, query, opts) { + opts || (opts = {}) + query || (query = {}) + + const collectionId = mapper.collection || underscore(mapper.name) + opts.select = `${collectionId}.${mapper.idAttribute}` + + return this._findAll(mapper, query, opts) + .then((result) => [result[0].length, { found: result[0].length }]) + }, + + _create (mapper, props, opts) { + props || (props = {}) + opts || (opts = {}) + + const createOpts = this.getOpt('createOpts', opts) + + return new utils.Promise((resolve, reject) => { + this.client.createDocument( + this.getCollectionLink(mapper, opts), + props, + createOpts, + (err, document) => { + if (err) { + return reject(err) + } + return resolve([document, { created: 1 }]) + } + ) + }) + }, + + _createMany (mapper, props, opts) { + props || (props = {}) + opts || (opts = {}) + + const insertOpts = this.getOpt('insertOpts', opts) + insertOpts.returnChanges = true + + return utils.Promise.all(props.map((record) => this._create(mapper, record, opts))) + .then((results) => results.map((result) => result[0])) + .then((results) => [results, { created: results.length }]) + }, + + _destroy (mapper, id, opts) { + opts || (opts = {}) + + const collLink = this.getCollectionLink(mapper, opts) + const deleteOpts = this.getOpt('deleteOpts', opts) + + return new utils.Promise((resolve, reject) => { + this.client.deleteDocument(`${collLink}/docs/${id}`, deleteOpts, (err) => { + if (err) { + if (err.code === 404) { + return resolve([undefined, { deleted: 0 }]) + } + return reject(err) + } + return resolve([undefined, { deleted: 1 }]) + }) + }) + }, + + _destroyAll (mapper, query, opts) { + query || (query = {}) + opts || (opts = {}) + + const destroyFn = (document) => this._destroy(mapper, document.id, opts) + + return this._findAll(mapper, query, opts) + .then((results) => utils.Promise.all(results[0].map(destroyFn))) + .then((results) => [undefined, { deleted: results.length }]) + }, + + _find (mapper, id, opts) { + opts || (opts = {}) + + const docLink = `${this.getCollectionLink(mapper, opts)}/docs/${id}` + + return new utils.Promise((resolve, reject) => { + this.client.readDocument(docLink, (err, document) => { + if (err) { + if (err.code === 404) { + return resolve([undefined, { found: 0 }]) + } + return reject(err) + } + return resolve([document, { found: document ? 1 : 0 }]) + }) + }) + }, + + _findAll (mapper, query, opts) { + opts || (opts = {}) + query || (query = {}) + + const collLink = this.getCollectionLink(mapper, opts) + const queryDocumentsOpts = this.getOpt('queryDocumentsOpts', opts) + + const querySpec = this.getQuerySpec(mapper, query, opts) + + return new utils.Promise((resolve, reject) => { + this.client.queryDocuments(collLink, querySpec, queryDocumentsOpts).toArray((err, documents) => { + if (err) { + return reject(err) + } + return resolve([documents, { found: documents.length }]) + }) + }) + }, + + _sum (mapper, field, query, opts) { + if (!utils.isString(field)) { + throw new Error('field must be a string!') + } + opts || (opts = {}) + query || (query = {}) + + const collectionId = mapper.collection || underscore(mapper.name) + opts.select = `${collectionId}.${mapper.idAttribute}, ${collectionId}.${field}` + + return this._findAll(mapper, query, opts) + .then((result) => { + const sum = result[0].reduce((sum, cur) => sum + cur[field], 0) + return [sum, { found: result[0].length }] + }) + }, + + _update (mapper, id, props, opts) { + props || (props = {}) + opts || (opts = {}) + + const updateOpts = this.getOpt('updateOpts', opts) + updateOpts.returnChanges = true + + return this.getCollectionLink(mapper, opts) + .get(id) + .update(props, updateOpts) + .run(this.getOpt('runOpts', opts)) + .then((cursor) => { + let record + this._handleErrors(cursor) + if (cursor && cursor.changes && cursor.changes.length && cursor.changes[0].new_val) { + record = cursor.changes[0].new_val + } else { + throw new Error('Not Found') + } + return [record, cursor] + }) + }, + + _updateAll (mapper, props, query, opts) { + props || (props = {}) + query || (query = {}) + opts || (opts = {}) + + const updateOpts = this.getOpt('updateOpts', opts) + updateOpts.returnChanges = true + + return this.filterSequence(this.getCollectionLink(mapper, opts), query) + .update(props, updateOpts) + .run(this.getOpt('runOpts', opts)) + .then((cursor) => { + let records = [] + this._handleErrors(cursor) + if (cursor && cursor.changes && cursor.changes.length) { + records = cursor.changes.map((change) => change.new_val) + } + return [records, cursor] + }) + }, + + _updateMany (mapper, records, opts) { + records || (records = []) + opts || (opts = {}) + + const insertOpts = this.getOpt('insertOpts', opts) + insertOpts.returnChanges = true + insertOpts.conflict = 'update' + + return this.getCollectionLink(mapper, opts) + .insert(records, insertOpts) + .run(this.getOpt('runOpts', opts)) + .then((cursor) => { + records = [] + this._handleErrors(cursor) + if (cursor && cursor.changes && cursor.changes.length) { + records = cursor.changes.map((change) => change.new_val) + } + return [records, cursor] + }) + }, + + _applyWhereFromObject (where) { + const fields = [] + const ops = [] + const predicates = [] + utils.forOwn(where, (clause, field) => { + if (!utils.isObject(clause)) { + clause = { + '==': clause + } + } + utils.forOwn(clause, (expr, op) => { + fields.push(field) + ops.push(op) + predicates.push(expr) + }) + }) + return { + fields, + ops, + predicates + } + }, + + _applyWhereFromArray (where) { + const groups = [] + where.forEach((_where, i) => { + if (utils.isString(_where)) { + return + } + const prev = where[i - 1] + const parser = utils.isArray(_where) ? this._applyWhereFromArray : this._applyWhereFromObject + const group = parser.call(this, _where) + if (prev === 'or') { + group.isOr = true + } + groups.push(group) + }) + groups.isArray = true + return groups + }, + + _testObjectGroup (sql, group, parameters, collectionId, opts) { + let i + const fields = group.fields + const ops = group.ops + const predicates = group.predicates + const len = ops.length + for (i = 0; i < len; i++) { + let op = ops[i] + const isOr = op.charAt(0) === '|' + op = isOr ? op.substr(1) : op + const predicateFn = this.getOperator(op, opts) + if (predicateFn) { + const subSql = predicateFn(fields[i], predicates[i], parameters, collectionId) + if (isOr) { + sql = sql ? `${sql} OR (${subSql})` : subSql + } else { + sql = sql ? `${sql} AND (${subSql})` : subSql + } + } else { + throw new Error(`Operator ${op} not supported!`) + } + } + return sql + }, + + _testArrayGroup (sql, groups, parameters, collectionId, opts) { + let i + const len = groups.length + for (i = 0; i < len; i++) { + const group = groups[i] + let subQuery + if (group.isArray) { + subQuery = this._testArrayGroup(sql, group, parameters, collectionId, opts) + } else { + subQuery = this._testObjectGroup(null, group, parameters, collectionId, opts) + } + if (groups[i - 1]) { + if (group.isOr) { + sql += ` OR (${subQuery})` + } else { + sql += ` AND (${subQuery})` + } + } else { + sql = sql ? sql + ` AND (${subQuery})` : subQuery + } + } + return sql + }, + + /** + * Apply the specified selection query to the provided RQL sequence. + * + * @name DocumentDBAdapter#getQuerySpec + * @method + * @param {Object} mapper The mapper. + * @param {Object} [query] Selection query. + * @param {Object} [query.where] Filtering criteria. + * @param {string|Array} [query.orderBy] Sorting criteria. + * @param {string|Array} [query.sort] Same as `query.sort`. + * @param {number} [query.limit] Limit results. + * @param {number} [query.skip] Offset results. + * @param {number} [query.offset] Same as `query.skip`. + * @param {Object} [opts] Configuration options. + * @param {Object} [opts.operators] Override the default predicate functions + * for specified operators. + */ + getQuerySpec (mapper, query, opts) { + query = utils.plainCopy(query || {}) + opts || (opts = {}) + opts.operators || (opts.operators = {}) + query.where || (query.where = {}) + query.orderBy || (query.orderBy = query.sort) + query.orderBy || (query.orderBy = []) + query.skip || (query.skip = query.offset) + const collectionId = mapper.collection || underscore(mapper.name) + + const select = opts.select || '*' + let sql = `${select} FROM ${collectionId}` + let whereSql + const parameters = [] + + // Transform non-keyword properties to "where" clause configuration + utils.forOwn(query, (config, keyword) => { + if (reserved.indexOf(keyword) === -1 && utils.isObject(query.where)) { + if (utils.isObject(config)) { + query.where[keyword] = config + } else { + query.where[keyword] = { + '==': config + } + } + delete query[keyword] + } + }) + + // Filter + let groups + + if (utils.isObject(query.where) && Object.keys(query.where).length !== 0) { + groups = this._applyWhereFromArray([query.where]) + } else if (utils.isArray(query.where)) { + groups = this._applyWhereFromArray(query.where) + } + + if (groups) { + whereSql = this._testArrayGroup(null, groups, parameters, collectionId, opts) + } + + if (whereSql) { + sql = `${sql} WHERE ${whereSql}` + } + + // Sort + if (query.orderBy) { + if (utils.isString(query.orderBy)) { + query.orderBy = [ + [query.orderBy, 'asc'] + ] + } + let orderBySql = '' + for (var i = 0; i < query.orderBy.length; i++) { + if (utils.isString(query.orderBy[i])) { + query.orderBy[i] = [query.orderBy[i], 'asc'] + } + const subOrderBySql = (query.orderBy[i][1] || '').toUpperCase() === 'DESC' ? `${collectionId}.${query.orderBy[i][0]} DESC` : `${collectionId}.${query.orderBy[i][0]}` + if (orderBySql) { + orderBySql = `${orderBySql}, ${subOrderBySql}` + } else { + orderBySql = subOrderBySql + } + } + if (orderBySql) { + orderBySql = `ORDER BY ${orderBySql}` + } + } + + // Offset + // if (query.skip) { + // sql += ` SKIP ${+query.skip}` + // } + + // Limit + if (query.limit) { + sql = `TOP ${+query.limit} ${sql}` + } + + // console.log(`sql: "${sql}"`) + // console.log('parameters', JSON.stringify(parameters, null, 2)) + return { + query: `SELECT ${sql}`, + parameters + } + }, + + getDbLink (opts) { + return `dbs/${opts.db === undefined ? this.documentOpts.db : opts.db}` + }, + + getCollectionLink (mapper, opts) { + return `${this.getDbLink(opts)}/colls/${mapper.collection || underscore(mapper.name)}` + }, + + waitForDb (opts) { + opts || (opts = {}) + const dbId = utils.isUndefined(opts.db) ? this.documentOpts.db : opts.db + if (!this.databases[dbId]) { + this.databases[dbId] = new utils.Promise((resolve, reject) => { + this.client.readDatabases().toArray((err, dbs) => { + if (err) { + return reject(err) + } + let existing + dbs.forEach((db) => { + if (dbId === db.id) { + existing = db + return false + } + }) + if (!existing) { + return this.client.createDatabase({ id: dbId }, (err, db) => { + if (err) { + return reject(err) + } + return resolve(db) + }) + } + return resolve(existing) + }) + }) + } + return this.databases[dbId] + }, + + waitForCollection (mapper, opts) { + opts || (opts = {}) + const collectionId = utils.isString(mapper) ? mapper : (mapper.collection || underscore(mapper.name)) + let dbId = utils.isUndefined(opts.db) ? this.documentOpts.db : opts.db + return this.waitForDb(opts).then(() => { + this.collections[dbId] = this.collections[dbId] || {} + if (!this.collections[dbId][collectionId]) { + this.collections[dbId][collectionId] = new utils.Promise((resolve, reject) => { + this.client.readCollections(`dbs/${dbId}`).toArray((err, collections) => { + if (err) { + return reject(err) + } + let existing + collections.forEach((collection) => { + if (collectionId === collection.id) { + existing = collection + return false + } + }) + if (!existing) { + return this.client.createCollection(`dbs/${dbId}`, { id: collectionId }, (err, collection) => { + if (err) { + return reject(err) + } + return resolve(collection) + }) + } + return resolve(existing) + }) + }) + } + return this.collections[dbId][collectionId] + }) + }, + + waitForIndex (table, index, opts) { + opts || (opts = {}) + // let db = utils.isUndefined(opts.db) ? this.documentOpts.db : opts.db + return this.waitForDb(opts).then(() => this.waitForCollection(table, opts)).then(() => { + // this.indices[db] = this.indices[db] || {} + // this.indices[db][table] = this.indices[db][table] || {} + // if (!this.collections[db][table][index]) { + // this.collections[db][table][index] = this.r.branch(this.r.db(db).table(table).indexList().contains(index), true, this.r.db(db).table(table).indexCreate(index)).run().then(() => { + // return this.r.db(db).table(table).indexWait(index).run() + // }) + // } + // return this.collections[db][table][index] + }) + }, + + /** + * Return the number of records that match the selection query. + * + * @name DocumentDBAdapter#count + * @method + * @param {Object} mapper the mapper. + * @param {Object} [query] Selection query. + * @param {Object} [query.where] Filtering criteria. + * @param {string|Array} [query.orderBy] Sorting criteria. + * @param {string|Array} [query.sort] Same as `query.sort`. + * @param {number} [query.limit] Limit results. + * @param {number} [query.skip] Offset results. + * @param {number} [query.offset] Same as `query.skip`. + * @param {Object} [opts] Configuration options. + * @param {Object} [opts.operators] Override the default predicate functions + * for specified operators. + * @param {boolean} [opts.raw=false] Whether to return a more detailed + * response object. + * @param {Object} [opts.runOpts] Options to pass to r#run. + * @return {Promise} + */ + count (mapper, query, opts) { + opts || (opts = {}) + query || (query = {}) + + return this.waitForCollection(mapper, opts) + .then(() => Adapter.prototype.count.call(this, mapper, query, opts)) + }, + + /** + * Create a new record. + * + * @name DocumentDBAdapter#create + * @method + * @param {Object} mapper The mapper. + * @param {Object} props The record to be created. + * @param {Object} [opts] Configuration options. + * @param {Object} [opts.insertOpts] Options to pass to r#insert. + * @param {boolean} [opts.raw=false] Whether to return a more detailed + * response object. + * @param {Object} [opts.runOpts] Options to pass to r#run. + * @return {Promise} + */ + create (mapper, props, opts) { + props || (props = {}) + opts || (opts = {}) + + return this.waitForCollection(mapper, opts) + .then(() => Adapter.prototype.create.call(this, mapper, props, opts)) + }, + + /** + * Create multiple records in a single batch. + * + * @name DocumentDBAdapter#createMany + * @method + * @param {Object} mapper The mapper. + * @param {Object} props The records to be created. + * @param {Object} [opts] Configuration options. + * @param {Object} [opts.insertOpts] Options to pass to r#insert. + * @param {boolean} [opts.raw=false] Whether to return a more detailed + * response object. + * @param {Object} [opts.runOpts] Options to pass to r#run. + * @return {Promise} + */ + createMany (mapper, props, opts) { + props || (props = {}) + opts || (opts = {}) + + return this.waitForCollection(mapper, opts) + .then(() => Adapter.prototype.createMany.call(this, mapper, props, opts)) + }, + + /** + * Destroy the record with the given primary key. + * + * @name DocumentDBAdapter#destroy + * @method + * @param {Object} mapper The mapper. + * @param {(string|number)} id Primary key of the record to destroy. + * @param {Object} [opts] Configuration options. + * @param {Object} [opts.deleteOpts] Options to pass to r#delete. + * @param {boolean} [opts.raw=false] Whether to return a more detailed + * response object. + * @param {Object} [opts.runOpts] Options to pass to r#run. + * @return {Promise} + */ + destroy (mapper, id, opts) { + opts || (opts = {}) + + return this.waitForCollection(mapper, opts) + .then(() => Adapter.prototype.destroy.call(this, mapper, id, opts)) + }, + + /** + * Destroy the records that match the selection query. + * + * @name DocumentDBAdapter#destroyAll + * @method + * @param {Object} mapper the mapper. + * @param {Object} [query] Selection query. + * @param {Object} [query.where] Filtering criteria. + * @param {string|Array} [query.orderBy] Sorting criteria. + * @param {string|Array} [query.sort] Same as `query.sort`. + * @param {number} [query.limit] Limit results. + * @param {number} [query.skip] Offset results. + * @param {number} [query.offset] Same as `query.skip`. + * @param {Object} [opts] Configuration options. + * @param {Object} [opts.deleteOpts] Options to pass to r#delete. + * @param {Object} [opts.operators] Override the default predicate functions + * for specified operators. + * @param {boolean} [opts.raw=false] Whether to return a more detailed + * response object. + * @param {Object} [opts.runOpts] Options to pass to r#run. + * @return {Promise} + */ + destroyAll (mapper, query, opts) { + opts || (opts = {}) + query || (query = {}) + + return this.waitForCollection(mapper, opts) + .then(() => Adapter.prototype.destroyAll.call(this, mapper, query, opts)) + }, + + /** + * Retrieve the record with the given primary key. + * + * @name DocumentDBAdapter#find + * @method + * @param {Object} mapper The mapper. + * @param {(string|number)} id Primary key of the record to retrieve. + * @param {Object} [opts] Configuration options. + * @param {boolean} [opts.raw=false] Whether to return a more detailed + * response object. + * @param {Object} [opts.runOpts] Options to pass to r#run. + * @param {string[]} [opts.with=[]] Relations to eager load. + * @return {Promise} + */ + find (mapper, id, opts) { + opts || (opts = {}) + opts.with || (opts.with = []) + + const relationList = mapper.relationList || [] + let tasks = [this.waitForCollection(mapper, opts)] + + relationList.forEach((def) => { + const relationName = def.relation + const relationDef = def.getRelation() + if (!opts.with || opts.with.indexOf(relationName) === -1) { + return + } + if (def.foreignKey && def.type !== 'belongsTo') { + if (def.type === 'belongsTo') { + tasks.push(this.waitForIndex(mapper.table || underscore(mapper.name), def.foreignKey, opts)) + } else { + tasks.push(this.waitForIndex(relationDef.table || underscore(relationDef.name), def.foreignKey, opts)) + } + } + }) + return Promise.all(tasks).then(() => Adapter.prototype.find.call(this, mapper, id, opts)) + }, + + /** + * Retrieve the records that match the selection query. + * + * @name DocumentDBAdapter#findAll + * @method + * @param {Object} mapper The mapper. + * @param {Object} [query] Selection query. + * @param {Object} [query.where] Filtering criteria. + * @param {string|Array} [query.orderBy] Sorting criteria. + * @param {string|Array} [query.sort] Same as `query.sort`. + * @param {number} [query.limit] Limit results. + * @param {number} [query.skip] Offset results. + * @param {number} [query.offset] Same as `query.skip`. + * @param {Object} [opts] Configuration options. + * @param {Object} [opts.operators] Override the default predicate functions + * for specified operators. + * @param {boolean} [opts.raw=false] Whether to return a more detailed + * response object. + * @param {Object} [opts.runOpts] Options to pass to r#run. + * @param {string[]} [opts.with=[]] Relations to eager load. + * @return {Promise} + */ + findAll (mapper, query, opts) { + opts || (opts = {}) + opts.with || (opts.with = []) + query || (query = {}) + + const relationList = mapper.relationList || [] + let tasks = [this.waitForCollection(mapper, opts)] + + relationList.forEach((def) => { + const relationName = def.relation + const relationDef = def.getRelation() + if (!opts.with || opts.with.indexOf(relationName) === -1) { + return + } + if (def.foreignKey && def.type !== 'belongsTo') { + if (def.type === 'belongsTo') { + tasks.push(this.waitForIndex(mapper.table || underscore(mapper.name), def.foreignKey, opts)) + } else { + tasks.push(this.waitForIndex(relationDef.table || underscore(relationDef.name), def.foreignKey, opts)) + } + } + }) + return Promise.all(tasks).then(() => Adapter.prototype.findAll.call(this, mapper, query, opts)) + }, + + /** + * Resolve the predicate function for the specified operator based on the + * given options and this adapter's settings. + * + * @name DocumentDBAdapter#getOperator + * @method + * @param {string} operator The name of the operator. + * @param {Object} [opts] Configuration options. + * @param {Object} [opts.operators] Override the default predicate functions + * for specified operators. + * @return {*} The predicate function for the specified operator. + */ + getOperator (operator, opts) { + opts || (opts = {}) + opts.operators || (opts.operators = {}) + let ownOps = this.operators || {} + return utils.isUndefined(opts.operators[operator]) ? ownOps[operator] : opts.operators[operator] + }, + + /** + * Return the sum of the specified field of records that match the selection + * query. + * + * @name DocumentDBAdapter#sum + * @method + * @param {Object} mapper The mapper. + * @param {string} field The field to sum. + * @param {Object} [query] Selection query. + * @param {Object} [query.where] Filtering criteria. + * @param {string|Array} [query.orderBy] Sorting criteria. + * @param {string|Array} [query.sort] Same as `query.sort`. + * @param {number} [query.limit] Limit results. + * @param {number} [query.skip] Offset results. + * @param {number} [query.offset] Same as `query.skip`. + * @param {Object} [opts] Configuration options. + * @param {Object} [opts.operators] Override the default predicate functions + * for specified operators. + * @param {boolean} [opts.raw=false] Whether to return a more detailed + * response object. + * @param {Object} [opts.runOpts] Options to pass to r#run. + * @return {Promise} + */ + sum (mapper, field, query, opts) { + opts || (opts = {}) + query || (query = {}) + + return this.waitForCollection(mapper, opts) + .then(() => Adapter.prototype.sum.call(this, mapper, field, query, opts)) + }, + + /** + * Apply the given update to the record with the specified primary key. + * + * @name DocumentDBAdapter#update + * @method + * @param {Object} mapper The mapper. + * @param {(string|number)} id The primary key of the record to be updated. + * @param {Object} props The update to apply to the record. + * @param {Object} [opts] Configuration options. + * @param {Object} [opts.updateOpts] Options to pass to r#update. + * @param {boolean} [opts.raw=false] Whether to return a more detailed + * response object. + * @param {Object} [opts.runOpts] Options to pass to r#run. + * @return {Promise} + */ + update (mapper, id, props, opts) { + props || (props = {}) + opts || (opts = {}) + + return this.waitForCollection(mapper, opts) + .then(() => Adapter.prototype.update.call(this, mapper, id, props, opts)) + }, + + /** + * Apply the given update to all records that match the selection query. + * + * @name DocumentDBAdapter#updateAll + * @method + * @param {Object} mapper The mapper. + * @param {Object} props The update to apply to the selected records. + * @param {Object} [query] Selection query. + * @param {Object} [query.where] Filtering criteria. + * @param {string|Array} [query.orderBy] Sorting criteria. + * @param {string|Array} [query.sort] Same as `query.sort`. + * @param {number} [query.limit] Limit results. + * @param {number} [query.skip] Offset results. + * @param {number} [query.offset] Same as `query.skip`. + * @param {Object} [opts] Configuration options. + * @param {Object} [opts.operators] Override the default predicate functions + * for specified operators. + * @param {boolean} [opts.raw=false] Whether to return a more detailed + * response object. + * @param {Object} [opts.runOpts] Options to pass to r#run. + * @param {Object} [opts.updateOpts] Options to pass to r#update. + * @return {Promise} + */ + updateAll (mapper, props, query, opts) { + props || (props = {}) + query || (query = {}) + opts || (opts = {}) + + return this.waitForCollection(mapper, opts) + .then(() => Adapter.prototype.updateAll.call(this, mapper, props, query, opts)) + }, + + /** + * Update the given records in a single batch. + * + * @name DocumentDBAdapter#updateMany + * @method + * @param {Object} mapper The mapper. + * @param {Object[]} records The records to update. + * @param {Object} [opts] Configuration options. + * @param {Object} [opts.insertOpts] Options to pass to r#insert. + * @param {boolean} [opts.raw=false] Whether to return a more detailed + * response object. + * @param {Object} [opts.runOpts] Options to pass to r#run. + * @return {Promise} + */ + updateMany (mapper, records, opts) { + records || (records = []) + opts || (opts = {}) + + return this.waitForCollection(mapper, opts) + .then(() => Adapter.prototype.updateMany.call(this, mapper, records, opts)) + } +}) + +/** + * Details of the current version of the `js-data-documentdb` module. + * + * @example ES2015 modules import + * import {version} from 'js-data-documentdb' + * console.log(version.full) + * + * @example CommonJS import + * var version = require('js-data-documentdb').version + * console.log(version.full) + * + * @name module:js-data-documentdb.version + * @type {Object} + * @property {string} version.full The full semver value. + * @property {number} version.major The major version number. + * @property {number} version.minor The minor version number. + * @property {number} version.patch The patch version number. + * @property {(string|boolean)} version.alpha The alpha version value, + * otherwise `false` if the current version is not alpha. + * @property {(string|boolean)} version.beta The beta version value, + * otherwise `false` if the current version is not beta. + */ +export const version = '<%= version %>' + +/** + * {@link DocumentDBAdapter} class. + * + * @example ES2015 modules import + * import {DocumentDBAdapter} from 'js-data-documentdb' + * const adapter = new DocumentDBAdapter() + * + * @example CommonJS import + * var DocumentDBAdapter = require('js-data-documentdb').DocumentDBAdapter + * var adapter = new DocumentDBAdapter() + * + * @name module:js-data-documentdb.DocumentDBAdapter + * @see DocumentDBAdapter + * @type {Constructor} + */ + +/** + * Registered as `js-data-documentdb` in NPM. + * + * @example Install from NPM + * npm i --save js-data-documentdb js-data documentdb + * + * @example ES2015 modules import + * import {DocumentDBAdapter} from 'js-data-documentdb' + * const adapter = new DocumentDBAdapter() + * + * @example CommonJS import + * var DocumentDBAdapter = require('js-data-documentdb').DocumentDBAdapter + * var adapter = new DocumentDBAdapter() + * + * @module js-data-documentdb + */ + +/** + * Create a subclass of this DocumentDBAdapter: + * @example DocumentDBAdapter.extend + * // Normally you would do: import {DocumentDBAdapter} from 'js-data-documentdb' + * const JSDataDocumentDB = require('js-data-documentdb') + * const {DocumentDBAdapter} = JSDataDocumentDB + * console.log('Using JSDataDocumentDB v' + JSDataDocumentDB.version.full) + * + * // Extend the class using ES2015 class syntax. + * class CustomDocumentDBAdapterClass extends DocumentDBAdapter { + * foo () { return 'bar' } + * static beep () { return 'boop' } + * } + * const customDocumentDBAdapter = new CustomDocumentDBAdapterClass() + * console.log(customDocumentDBAdapter.foo()) + * console.log(CustomDocumentDBAdapterClass.beep()) + * + * // Extend the class using alternate method. + * const OtherDocumentDBAdapterClass = DocumentDBAdapter.extend({ + * foo () { return 'bar' } + * }, { + * beep () { return 'boop' } + * }) + * const otherDocumentDBAdapter = new OtherDocumentDBAdapterClass() + * console.log(otherDocumentDBAdapter.foo()) + * console.log(OtherDocumentDBAdapterClass.beep()) + * + * // Extend the class, providing a custom constructor. + * function AnotherDocumentDBAdapterClass () { + * DocumentDBAdapter.call(this) + * this.created_at = new Date().getTime() + * } + * DocumentDBAdapter.extend({ + * constructor: AnotherDocumentDBAdapterClass, + * foo () { return 'bar' } + * }, { + * beep () { return 'boop' } + * }) + * const anotherDocumentDBAdapter = new AnotherDocumentDBAdapterClass() + * console.log(anotherDocumentDBAdapter.created_at) + * console.log(anotherDocumentDBAdapter.foo()) + * console.log(AnotherDocumentDBAdapterClass.beep()) + * + * @method DocumentDBAdapter.extend + * @param {Object} [props={}] Properties to add to the prototype of the + * subclass. + * @param {Object} [props.constructor] Provide a custom constructor function + * to be used as the subclass itself. + * @param {Object} [classProps={}] Static properties to add to the subclass. + * @returns {Constructor} Subclass of this DocumentDBAdapter class. + * @since 3.0.0 + */ diff --git a/typings.json b/typings.json new file mode 100644 index 0000000..854f127 --- /dev/null +++ b/typings.json @@ -0,0 +1,8 @@ +{ + "name": "js-data-documentdb", + "version": false, + "main": "./dist/js-data-documentdb.d.ts", + "ambientDependencies": { + "es6-shim": "registry:dt/es6-shim" + } +}