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" + } +}