import { utils } from 'js-data'
import {
Adapter,
reserved
} from 'js-data-adapter'
import { DocumentClient } from 'documentdb'
import underscore from 'mout/string/underscore'
const REQUEST_OPTS_DEFAULTS = {}
const FEED_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. These produce the
* appropriate SQL and add the necessary parameters.
*
* @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} 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 `ARRAY_CONTAINS(${addParameter(field, value, parameters)}, ${collectionId}.${field})`
},
'notIn': function (field, value, parameters, collectionId) {
// return `${collectionId}.${field} NOT IN ${addParameter(field, value, parameters)}`
return `NOT ARRAY_CONTAINS(${addParameter(field, value, parameters)}, ${collectionId}.${field})`
},
'contains': function (field, value, parameters, collectionId) {
return `ARRAY_CONTAINS(${collectionId}.${field}, ${addParameter(field, value, parameters)})`
},
'notContains': function (field, value, parameters, collectionId) {
return `NOT ARRAY_CONTAINS(${collectionId}.${field}, ${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({
* documentOpts: {
* db: 'mydb',
* urlConnection: process.env.DOCUMENT_DB_ENDPOINT,
* auth: {
* masterKey: process.env.DOCUMENT_DB_KEY
* }
* }
* });
*
* // 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 {object} [opts.client] See {@link DocumentDBAdapter#client}.
* @param {boolean} [opts.debug=false] See {@link Adapter#debug}.
* @param {object} [opts.documentOpts={}] See {@link DocumentDBAdapter#documentOpts}.
* @param {object} [opts.feedOpts={}] See {@link DocumentDBAdapter#feedOpts}.
* @param {object} [opts.operators={@link module:js-data-documentdb.OPERATORS}] See {@link DocumentDBAdapter#operators}.
* @param {boolean} [opts.raw=false] See {@link Adapter#raw}.
* @param {object} [opts.requestOpts={}] See {@link DocumentDBAdapter#requestOpts}.
*/
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 <caption>Use default instance.</caption>
* import { DocumentDBAdapter } from 'js-data-documentdb';
* const adapter = new DocumentDBAdapter()
* adapter.client.createDatabase('foo', function (err, db) {...});
*
* @example <caption>Configure default instance.</caption>
* import { DocumentDBAdapter } from 'js-data-documentdb';
* const adapter = new DocumentDBAdapter({
* documentOpts: {
* db: 'mydb',
* urlConnection: process.env.DOCUMENT_DB_ENDPOINT,
* auth: {
* masterKey: process.env.DOCUMENT_DB_KEY
* }
* }
* });
* adapter.client.createDatabase('foo', function (err, db) {...});
*
* @example <caption>Provide a custom instance.</caption>
* 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#client
* @type {object}
*/
client: {
writable: true,
value: undefined
},
databases: {
value: {}
},
indices: {
value: {}
},
collections: {
value: {}
}
})
Adapter.call(this, opts)
/**
* Default options to pass to DocumentClient requests.
*
* @name DocumentDBAdapter#requestOpts
* @type {object}
* @default {}
*/
this.requestOpts || (this.requestOpts = {})
utils.fillIn(this.requestOpts, REQUEST_OPTS_DEFAULTS)
/**
* Default options to pass to DocumentClient#queryDocuments.
*
* @name DocumentDBAdapter#feedOpts
* @type {object}
* @default {}
*/
this.feedOpts || (this.feedOpts = {})
utils.fillIn(this.feedOpts, FEED_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} db The default database to use.
* @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 = {})
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 = {})
return new utils.Promise((resolve, reject) => {
this.client.createDocument(
this.getCollectionLink(mapper, opts),
utils.plainCopy(props),
this.getOpt('requestOpts', opts),
(err, document) => {
if (err) {
return reject(err)
}
return resolve([document, { created: 1 }])
}
)
})
},
_createMany (mapper, props, opts) {
props || (props = {})
opts || (opts = {})
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 requestOpts = this.getOpt('requestOpts', opts)
return new utils.Promise((resolve, reject) => {
this.client.deleteDocument(`${collLink}/docs/${id}`, requestOpts, (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}`
const requestOpts = this.getOpt('requestOpts', opts)
return new utils.Promise((resolve, reject) => {
this.client.readDocument(docLink, requestOpts, (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 feedOpts = this.getOpt('feedOpts', opts)
const querySpec = this.getQuerySpec(mapper, query, opts)
return new utils.Promise((resolve, reject) => {
this.client.queryDocuments(collLink, querySpec, feedOpts).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 docLink = `${this.getCollectionLink(mapper, opts)}/docs/${id}`
const requestOpts = this.getOpt('requestOpts', opts)
return this._find(mapper, id, opts)
.then((result) => {
const document = result[0]
if (!document) {
throw new Error('Not Found')
}
utils.deepMixIn(document, utils.plainCopy(props))
return new utils.Promise((resolve, reject) => {
this.client.replaceDocument(docLink, document, requestOpts, (err, updatedDocument) => {
if (err) {
return reject(err)
}
return resolve([updatedDocument, { updated: updatedDocument ? 1 : 0 }])
})
})
})
},
_updateAll (mapper, props, query, opts) {
props || (props = {})
query || (query = {})
opts || (opts = {})
props = utils.plainCopy(props)
const requestOpts = this.getOpt('requestOpts', opts)
const collLink = this.getCollectionLink(mapper, opts)
return this._findAll(mapper, query, opts)
.then((result) => {
const documents = result[0]
documents.forEach((document) => {
utils.deepMixIn(document, props)
})
return utils.Promise.all(documents.map((document) => {
return new utils.Promise((resolve, reject) => {
const docLink = `${collLink}/docs/${document.id}`
this.client.replaceDocument(docLink, document, requestOpts, (err, updatedDocument) => {
if (err) {
return reject(err)
}
return resolve(updatedDocument)
})
})
}))
})
.then((documents) => [documents, { updated: documents.length }])
},
_updateMany (mapper, records, opts) {
records || (records = [])
opts || (opts = {})
records = records.filter((record) => record && record.id !== undefined)
return utils.Promise.all(records.map((record) => this._update(mapper, record.id, record, opts)))
.then((results) => [results.map((result) => result[0]), { updated: results.length }])
},
_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
},
/**
* Generate the querySpec object for DocumentClient#queryDocuments.
*
* @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)
if (utils.isString(opts.fields)) {
opts.fields = [opts.fields]
}
const collectionId = mapper.collection || underscore(mapper.name)
let select = '*'
let whereSql
const parameters = []
if (utils.isString(opts.select)) {
select = opts.select
} else if (utils.isArray(opts.fields)) {
select = opts.fields.map((field) => `${collectionId}.${field}`).join(',')
}
let sql = `${select} FROM ${collectionId}`
// 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
let orderBySql = ''
if (query.orderBy) {
if (utils.isString(query.orderBy)) {
query.orderBy = [
[query.orderBy, 'asc']
]
}
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}`
}
sql = `SELECT ${sql}` + (orderBySql ? ` ${orderBySql}` : '')
return {
query: 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]
})
},
/**
* 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.requestOpts] Options to pass to the DocumentClient request.
* @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 {boolean} [opts.raw=false] Whether to return a more detailed
* response object.
* @param {object} [opts.requestOpts] Options to pass to the DocumentClient request.
* @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 {boolean} [opts.raw=false] Whether to return a more detailed
* response object.
* @param {object} [opts.requestOpts] Options to pass to the DocumentClient request.
* @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 {boolean} [opts.raw=false] Whether to return a more detailed
* response object.
* @param {object} [opts.requestOpts] Options to pass to the DocumentClient request.
* @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.feedOpts] Options to pass to the DocumentClient#queryDocuments.
* @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.requestOpts] Options to pass to the DocumentClient request.
* @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.requestOpts] Options to pass to the DocumentClient request.
* @param {string[]} [opts.with=[]] Relations to eager load.
* @return {Promise}
*/
find (mapper, id, opts) {
opts || (opts = {})
return this.waitForCollection(mapper, opts)
.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.feedOpts] Options to pass to the DocumentClient#queryDocuments.
* @param {string[]} [opts.fields] Choose which fields should be returned from
* the SQL query, e.g. ["id", "name"].
* @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.requestOpts] Options to pass to the DocumentClient request.
* @param {string} [opts.select] Override the SELECT string in the resulting
* SQL query, e.g. "users.id,users.name".
* @param {string[]} [opts.with=[]] Relations to eager load.
* @return {Promise}
*/
findAll (mapper, query, opts) {
opts || (opts = {})
query || (query = {})
return this.waitForCollection(mapper, opts)
.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.feedOpts] Options to pass to the DocumentClient#queryDocuments.
* @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.requestOpts] Options to pass to the DocumentClient request.
* @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 {boolean} [opts.raw=false] Whether to return a more detailed
* response object.
* @param {object} [opts.requestOpts] Options to pass to the DocumentClient request.
* @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.feedOpts] Options to pass to the DocumentClient#queryDocuments.
* @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.requestOpts] Options to pass to the DocumentClient request.
* @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 {boolean} [opts.raw=false] Whether to return a more detailed
* response object.
* @param {object} [opts.requestOpts] Options to pass to the DocumentClient request.
* @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 <caption>ES2015 modules import</caption>
* import {version} from 'js-data-documentdb'
* console.log(version.full)
*
* @example <caption>CommonJS import</caption>
* 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 <caption>ES2015 modules import</caption>
* import {DocumentDBAdapter} from 'js-data-documentdb'
* const adapter = new DocumentDBAdapter()
*
* @example <caption>CommonJS import</caption>
* 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 <caption>Install from NPM</caption>
* npm i --save js-data-documentdb js-data documentdb
*
* @example <caption>ES2015 modules import</caption>
* import { DocumentDBAdapter } from 'js-data-documentdb'
* const adapter = new DocumentDBAdapter({
* documentOpts: {
* db: 'mydb',
* urlConnection: process.env.DOCUMENT_DB_ENDPOINT,
* auth: {
* masterKey: process.env.DOCUMENT_DB_KEY
* }
* }
* })
*
* @example <caption>CommonJS import</caption>
* var DocumentDBAdapter = require('js-data-documentdb').DocumentDBAdapter
* var adapter = new DocumentDBAdapter({
* documentOpts: {
* db: 'mydb',
* urlConnection: process.env.DOCUMENT_DB_ENDPOINT,
* auth: {
* masterKey: process.env.DOCUMENT_DB_KEY
* }
* }
* })
*
* @module js-data-documentdb
*/
/**
* Create a subclass of this DocumentDBAdapter:
* @example <caption>DocumentDBAdapter.extend</caption>
* // 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({
* documentOpts: {
* db: 'mydb',
* urlConnection: process.env.DOCUMENT_DB_ENDPOINT,
* auth: {
* masterKey: process.env.DOCUMENT_DB_KEY
* }
* }
* })
* 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({
* documentOpts: {
* db: 'mydb',
* urlConnection: process.env.DOCUMENT_DB_ENDPOINT,
* auth: {
* masterKey: process.env.DOCUMENT_DB_KEY
* }
* }
* })
* 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({
* documentOpts: {
* db: 'mydb',
* urlConnection: process.env.DOCUMENT_DB_ENDPOINT,
* auth: {
* masterKey: process.env.DOCUMENT_DB_KEY
* }
* }
* })
* 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
*/