lib/nano.js (1,018 lines of code) (raw):

// Licensed under the Apache License, Version 2.0 (the 'License'); you may not // use this file except in compliance with the License. You may obtain a copy of // the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations under // the License. const { URL } = require('url') const http = require('http') const https = require('https') const assert = require('assert') const querystring = require('qs') const axios = require('axios') const stream = require('stream') const pkg = require('../package.json') const AGENT_DEFAULTS = { keepAlive: true, maxSockets: 50, keepAliveMsecs: 30000 } const defaultHttpAgent = new http.Agent(AGENT_DEFAULTS) const defaultHttpsAgent = new https.Agent(AGENT_DEFAULTS) const SCRUBBED_STR = 'XXXXXX' const ChangesReader = require('./changesreader.js') const CookieJar = require('./cookie.js') const MultiPartFactory = require('./multipart.js') function isEmpty (val) { return val == null || !(Object.keys(val) || val).length } function getCallback (opts, callback) { if (typeof opts === 'function') { callback = opts opts = {} } opts = opts || {} return { opts, callback } } // feed this any number of arguements, it will return true if // any of them are missing (falsey) function missing (...params) { return params.some(param => !param) } // calls the supplied callback or if not supplied, returns a rejected promise function callbackOrRejectError (callback, error = new Error('Invalid parameters')) { if (callback) { return callback(error, null) } else { return Promise.reject(error) } } module.exports = exports = function dbScope (cfg) { let serverScope = {} if (typeof cfg === 'string') { cfg = { url: cfg } } assert.strictEqual(typeof cfg, 'object', 'You must specify the endpoint url when invoking this module') assert.ok(/^https?:/.test(cfg.url), 'url is not valid') cfg = Object.assign({}, cfg) serverScope.config = cfg cfg.requestDefaults = cfg.requestDefaults || {} const dummyLogger = () => {} const log = typeof cfg.log === 'function' ? cfg.log : dummyLogger const parseUrl = 'parseUrl' in cfg ? cfg.parseUrl : true // create cookieJar for this Nano cfg.cookieJar = new CookieJar() function maybeExtractDatabaseComponent () { if (!parseUrl) { return } const path = new URL(cfg.url) const pathArray = path.pathname.split('/').filter(function (e) { return e }) const db = pathArray.pop() const rootPath = path.pathname.replace(/\/?$/, '/..') if (db) { cfg.url = urlResolveFix(cfg.url, rootPath).replace(/\/?$/, '') return db } } function scrubURL (str) { if (str) { str = str.replace(/\/\/(.*)@/, `//${SCRUBBED_STR}:${SCRUBBED_STR}@`) } return str } function scrubRequest (req, cloned) { // scrub credentials req.url = scrubURL(req.url) if (req.headers.cookie) { req.headers.cookie = 'XXXXXXX' } if (req.auth) { if (!cloned) { req.auth = JSON.parse(JSON.stringify(req.auth)) // clone just auth if not already cloned } req.auth.username = SCRUBBED_STR req.auth.password = SCRUBBED_STR } } const responseHandler = function (response, req, opts, resolve, reject, callback) { const statusCode = response.status || (response.response && response.response.status) || 500 if (response.isAxiosError && response.response) { response = response.response } let body = response.data response.statusCode = statusCode // cookie parsing if (response.headers) { const h = response.headers['set-cookie'] if (h && h.length) { h.forEach((header) => { cfg.cookieJar.parse(header, req.url) }) } } // let parsed const responseHeaders = Object.assign({ uri: scrubURL(req.url), statusCode }, response.headers) if (!response.status) { if (axios.isCancel(response)) { if (resolve) { resolve('canceled') } if (callback) { callback(null, 'canceled', responseHeaders) } return } log({ err: 'socket', body, headers: responseHeaders }) if (reject) { reject(new Error(`error happened in your connection. Reason: ${response.message}`)) } if (callback) { const returnError = new Error(`error happened in your connection. Reason: ${response.message}`) returnError.scope = 'socket' returnError.errid = 'request' callback(returnError) } return } delete responseHeaders.server delete responseHeaders['content-length'] /* if (opts.dontParse) { parsed = body } else { try { parsed = JSON.parse(body) } catch (err) { parsed = body } } */ if (statusCode >= 200 && statusCode < 400) { log({ err: null, body, headers: responseHeaders }) if (resolve) { resolve(body) } if (callback) { callback(null, body, responseHeaders) } return } // cloudant stacktrace if (typeof body === 'string') { body = { message: body } } if (!body.message && (body.reason || body.error)) { body.message = (body.reason || body.error) } // fix cloudant issues where they give an erlang stacktrace as js delete body.stack // scrub credentials scrubRequest(req) log({ err: 'couch', body, headers: responseHeaders }) const message = body.message || 'couch returned ' + statusCode const errors = new Error(message) errors.scope = 'couch' errors.statusCode = statusCode errors.request = req errors.headers = responseHeaders errors.errid = 'non_200' errors.name = 'Error' errors.description = message // add any attributes from the HTTP response into the // Error object (except message, which would overwrite // the text message of the Error) delete body.message Object.assign(errors, body) if (reject) { reject(errors) } if (callback) { callback(errors) } } const streamResponseHandler = function (response, req, stream) { const statusCode = response.status || (response.response && response.response.status) || 500 if (response.isAxiosError && response.response) { response = response.response } const message = response.statusText scrubRequest(req) const responseHeaders = Object.assign({ uri: req.url, statusCode }, response.headers) const error = new Error(message) error.scope = 'couch' error.statusCode = statusCode error.request = req error.headers = responseHeaders error.errid = 'non_200' error.name = 'Error' error.description = message error.reason = message log({ err: 'couch', body: message, headers: responseHeaders }) stream.emit('error', error) } function relax (opts, callback) { if (typeof opts === 'function') { callback = opts opts = { path: '' } } if (typeof opts === 'string') { opts = { path: opts } } if (!opts) { opts = { path: '' } callback = null } const qs = Object.assign({}, opts.qs) const headers = { 'content-type': 'application/json', accept: 'application/json', 'user-agent': `${pkg.name}/${pkg.version} (Node.js ${process.version})`, 'Accept-Encoding': 'deflate, gzip' } const req = Object.assign({ method: (opts.method || 'GET'), headers, uri: cfg.url }, { ...cfg.requestDefaults, headers: Object.assign(headers, cfg.requestDefaults && cfg.requestDefaults.headers ? cfg.requestDefaults.headers : {}) }) // https://github.com/mikeal/request#requestjar const isJar = opts.jar || cfg.jar || (cfg.requestDefaults && cfg.requestDefaults.jar) if (opts.signal) { req.signal = opts.signal } if (isJar) { req.withCredentials = true } // http://wiki.apache.org/couchdb/HTTP_database_API#Naming_and_Addressing if (opts.db) { req.uri = urlResolveFix(req.uri, encodeURIComponent(opts.db)) } if (opts.multipart) { // generate the multipart/related body, header and boundary to // upload multiple binary attachments in one request const mp = new MultiPartFactory(opts.multipart) opts.contentType = mp.header req.body = mp.data } req.headers = Object.assign(req.headers, opts.headers, cfg.defaultHeaders) if (opts.path) { req.uri += '/' + opts.path } else if (opts.doc) { if (!/^_design|_local/.test(opts.doc)) { // http://wiki.apache.org/couchdb/HTTP_Document_API#Naming.2FAddressing req.uri += '/' + encodeURIComponent(opts.doc) } else { // http://wiki.apache.org/couchdb/HTTP_Document_API#Document_IDs req.uri += '/' + opts.doc } // http://wiki.apache.org/couchdb/HTTP_Document_API#Attachments if (opts.att) { req.uri += '/' + opts.att } } // prevent bugs where people set encoding when piping if (opts.encoding !== undefined) { req.encoding = opts.encoding delete req.headers['content-type'] delete req.headers.accept } if (opts.contentType) { req.headers['content-type'] = opts.contentType delete req.headers.accept } if (opts.accept) { req.headers.accept = opts.accept } // http://guide.couchdb.org/draft/security.html#cookies if (cfg.cookie) { req.headers['X-CouchDB-WWW-Authenticate'] = 'Cookie' req.headers.cookie = cfg.cookie } // http://wiki.apache.org/couchdb/HTTP_view_API#Querying_Options if (typeof opts.qs === 'object' && !isEmpty(opts.qs)) { ['startkey', 'endkey', 'key', 'keys', 'start_key', 'end_key'].forEach(function (key) { if (key in opts.qs) { qs[key] = JSON.stringify(opts.qs[key]) } }) req.qs = qs } // add any cookies for this domain const cookie = cfg.cookieJar.getCookieString(req.uri) if (cookie) { req.headers.cookie = cookie } if (opts.body) { if (Buffer.isBuffer(opts.body) || opts.dontStringify) { req.body = opts.body } else { req.body = JSON.stringify(opts.body, function (key, value) { // don't encode functions if (typeof (value) === 'function') { return value.toString() } else { return value } }) } } if (opts.form) { req.headers['content-type'] = 'application/x-www-form-urlencoded; charset=utf-8' req.body = querystring.stringify(opts.form).toString('utf8') } // ask request to render query string arrays as repeated values e.g. // ?drilldown=["author","Dickens"]&drilldown=["publisher","Penguin"] req.qsStringifyOptions = { arrayFormat: 'repeat' } // This where the HTTP request is made. // Nano used to use the now-deprecated "request" library but now we're going to // use axios, so let's modify the "req" object to suit axios req.url = req.uri delete req.uri req.method = req.method.toLowerCase() req.params = req.qs delete req.qs req.paramsSerializer = { serialize: (params) => querystring.stringify(params, { arrayFormat: 'brackets' }) } req.data = req.body delete req.body req.maxRedirects = 0 if (opts.stream) { req.responseType = 'stream' } else if (opts.dontParse) { req.responseType = 'arraybuffer' } // scrub and log const scrubbedReq = { method: req.method, headers: JSON.parse(JSON.stringify(req.headers)), url: req.url } scrubRequest(scrubbedReq, true) log(scrubbedReq) // add http agents req.httpAgent = cfg.requestDefaults.agent || defaultHttpAgent req.httpsAgent = cfg.requestDefaults.agent || defaultHttpsAgent const ax = axios.create({ httpAgent: req.httpAgent, httpsAgent: req.httpsAgent }) // actually do the HTTP request if (opts.stream) { // return the Request object for streaming const outStream = new stream.PassThrough() ax(req) .then((response) => { response.data.pipe(outStream) }).catch(e => { streamResponseHandler(e, req, outStream) }) return outStream } else { if (typeof callback === 'function') { ax(req).then((response) => { responseHandler(response, req, opts, null, null, callback) }).catch((e) => { responseHandler(e, req, opts, null, null, callback) }) } else { return new Promise((resolve, reject) => { ax(req).then((response) => { responseHandler(response, req, opts, resolve, reject) }).catch((e) => { responseHandler(e, req, opts, resolve, reject) }) }) } } } // http://docs.couchdb.org/en/latest/api/server/authn.html#cookie-authentication function auth (username, password, callback) { return relax({ method: 'POST', db: '_session', form: { name: username, password } }, callback) } // http://docs.couchdb.org/en/latest/api/server/authn.html#post--_session function session (callback) { return relax({ db: '_session' }, callback) } // https://docs.couchdb.org/en/latest/api/server/common.html#api-server-root function info (callback) { return relax({ path: '' }, callback) } // http://docs.couchdb.org/en/latest/api/server/common.html#get--_db_updates function updates (qs0, callback0) { const { opts, callback } = getCallback(qs0, callback0) return relax({ db: '_db_updates', qs: opts }, callback) } // http://docs.couchdb.org/en/latest/api/database/common.html#put--db function createDb (dbName, qs0, callback0) { const { opts, callback } = getCallback(qs0, callback0) if (missing(dbName)) { return callbackOrRejectError(callback) } return relax({ db: dbName, method: 'PUT', qs: opts }, callback) } // http://docs.couchdb.org/en/latest/api/database/common.html#delete--db function destroyDb (dbName, callback) { if (missing(dbName)) { return callbackOrRejectError(callback) } return relax({ db: dbName, method: 'DELETE' }, callback) } // http://docs.couchdb.org/en/latest/api/database/common.html#get--db function getDb (dbName, callback) { if (missing(dbName)) { return callbackOrRejectError(callback) } return relax({ db: dbName }, callback) } // http://docs.couchdb.org/en/latest/api/server/common.html#get--_all_dbs function listDbs (callback) { return relax({ db: '_all_dbs' }, callback) } // http://docs.couchdb.org/en/latest/api/server/common.html#get--_all_dbs function listDbsAsStream () { return relax({ db: '_all_dbs', stream: true }) } // http://docs.couchdb.org/en/latest/api/database/compact.html#post--db-_compact function compactDb (dbName, ddoc, callback) { if (typeof ddoc === 'function') { callback = ddoc ddoc = null } if (missing(dbName)) { return callbackOrRejectError(callback) } return relax({ db: dbName, doc: '_compact', att: ddoc, method: 'POST' }, callback) } // http://docs.couchdb.org/en/latest/api/database/changes.html#get--db-_changes function changesDb (dbName, qs0, callback0) { const { opts, callback } = getCallback(qs0, callback0) if (missing(dbName)) { return callbackOrRejectError(callback) } return relax({ db: dbName, path: '_changes', qs: opts }, callback) } function changesDbAsStream (dbName, opts) { return relax({ db: dbName, path: '_changes', stream: true, qs: opts }) } function _serializeAsUrl (db) { if (typeof db === 'object' && db.config && db.config.url && db.config.db) { return urlResolveFix(db.config.url, encodeURIComponent(db.config.db)) } else { try { // if it parses, return it const parsed = new URL(db) return parsed.toString() } catch (e) { // otherwise treat it as a database name return urlResolveFix(cfg.url, encodeURIComponent(db)) } } } // http://docs.couchdb.org/en/latest/api/server/common.html#post--_replicate function replicateDb (source, target, opts0, callback0) { const { opts, callback } = getCallback(opts0, callback0) if (missing(source, target)) { return callbackOrRejectError(callback) } // _replicate opts.source = _serializeAsUrl(source) opts.target = _serializeAsUrl(target) return relax({ db: '_replicate', body: opts, method: 'POST' }, callback) } // http://docs.couchdb.org/en/latest/api/server/common.html#uuids function uuids (count, callback) { if (typeof count === 'function') { callback = count count = 1 } count = count || 1 return relax({ method: 'GET', path: '_uuids', qs: { count } }, callback) } // http://guide.couchdb.org/draft/replication.html function enableReplication (source, target, opts0, callback0) { const { opts, callback } = getCallback(opts0, callback0) if (missing(source, target)) { return callbackOrRejectError(callback) } // _replicator opts.source = _serializeAsUrl(source) opts.target = _serializeAsUrl(target) return relax({ db: '_replicator', body: opts, method: 'POST' }, callback) } // http://guide.couchdb.org/draft/replication.html function queryReplication (id, opts0, callback0) { const { opts, callback } = getCallback(opts0, callback0) if (missing(id)) { return callbackOrRejectError(callback) } return relax({ db: '_replicator', method: 'GET', path: id, qs: opts }, callback) } // http://guide.couchdb.org/draft/replication.html function disableReplication (id, rev, opts0, callback0) { const { opts, callback } = getCallback(opts0, callback0) if (missing(id, rev)) { return callbackOrRejectError(callback) } const req = { db: '_replicator', method: 'DELETE', path: id, qs: Object.assign(opts, { rev }) } return relax(req, callback) } function docModule (dbName) { let docScope = {} dbName = decodeURIComponent(dbName) // http://docs.couchdb.org/en/latest/api/document/common.html#put--db-docid // http://docs.couchdb.org/en/latest/api/database/common.html#post--db function insertDoc (doc, qs0, callback0) { const req = { db: dbName, body: doc, method: 'POST' } let { opts, callback } = getCallback(qs0, callback0) if (typeof opts === 'string') { opts = { docName: opts } } if (opts) { if (opts.docName) { req.doc = opts.docName req.method = 'PUT' delete opts.docName } req.qs = opts } return relax(req, callback) } // http://docs.couchdb.org/en/latest/api/document/common.html#delete--db-docid function destroyDoc (docName, rev, callback) { if (missing(docName)) { return callbackOrRejectError(callback) } return relax({ db: dbName, doc: docName, method: 'DELETE', qs: { rev } }, callback) } // http://docs.couchdb.org/en/latest/api/document/common.html#get--db-docid function getDoc (docName, qs0, callback0) { const { opts, callback } = getCallback(qs0, callback0) if (missing(docName)) { return callbackOrRejectError(callback) } return relax({ db: dbName, doc: docName, qs: opts }, callback) } // http://docs.couchdb.org/en/latest/api/document/common.html#head--db-docid function headDoc (docName, callback) { if (missing(docName)) { return callbackOrRejectError(callback) } if (callback) { relax({ db: dbName, doc: docName, method: 'HEAD', qs: {} }, callback) } else { // this function doesn't pass on the Promise from relax because it needs // to return the headers when resolving the Promise return new Promise(function (resolve, reject) { relax({ db: dbName, doc: docName, method: 'HEAD', qs: {} }, function (err, body, headers) { if (err) { reject(err) } else { resolve(headers) } }) }) } } // http://docs.couchdb.org/en/latest/api/database/bulk-api.html#get--db-_all_docs function listDoc (qs0, callback0) { const { opts, callback } = getCallback(qs0, callback0) return relax({ db: dbName, path: '_all_docs', qs: opts }, callback) } // http://docs.couchdb.org/en/latest/api/database/bulk-api.html#get--db-_all_docs function listDocAsStream (opts) { return relax({ db: dbName, path: '_all_docs', qs: opts, stream: true }) } // http://docs.couchdb.org/en/latest/api/database/bulk-api.html#post--db-_all_docs function fetchDocs (docNames, qs0, callback0) { const { opts, callback } = getCallback(qs0, callback0) opts.include_docs = true if (missing(docNames) || typeof docNames !== 'object' || !docNames.keys || !Array.isArray(docNames.keys) || docNames.keys.length === 0) { return callbackOrRejectError(callback) } return relax({ db: dbName, path: '_all_docs', method: 'POST', qs: opts, body: docNames }, callback) } function fetchRevs (docNames, qs0, callback0) { const { opts, callback } = getCallback(qs0, callback0) if (missing(docNames) || typeof docNames !== 'object' || !docNames.keys || !Array.isArray(docNames.keys) || docNames.keys.length === 0) { return callbackOrRejectError(callback) } return relax({ db: dbName, path: '_all_docs', method: 'POST', qs: opts, body: docNames }, callback) } function view (ddoc, viewName, meta, qs0, callback0) { const { opts, callback } = getCallback(qs0, callback0) if (missing(ddoc, viewName) && !meta.viewPath) { return callbackOrRejectError(callback) } if (typeof meta.stream !== 'boolean') { meta.stream = false } // prevent mutation of the client qs object by using a clone const qs1 = Object.assign({}, opts) const viewPath = meta.viewPath || '_design/' + ddoc + '/_' + meta.type + '/' + viewName if (meta.type === 'search') { return relax({ db: dbName, path: viewPath, method: 'POST', body: qs1, stream: meta.stream }, callback) } else if (qs1 && qs1.keys) { const body = { keys: qs1.keys } delete qs1.keys return relax({ db: dbName, path: viewPath, method: 'POST', qs: qs1, body, stream: meta.stream }, callback) } else if (qs1 && qs1.queries) { const body = { queries: qs1.queries } delete qs1.queries return relax({ db: dbName, path: viewPath, method: 'POST', qs: qs1, body }, callback) } else { const req = { db: dbName, method: meta.method || 'GET', path: viewPath, qs: qs1, stream: meta.stream } if (meta.body) { req.body = meta.body } return relax(req, callback) } } // http://docs.couchdb.org/en/latest/api/ddoc/views.html#post--db-_design-ddoc-_view-view function viewDocs (ddoc, viewName, qs, callback) { return view(ddoc, viewName, { type: 'view' }, qs, callback) } // http://docs.couchdb.org/en/latest/api/ddoc/views.html#post--db-_design-ddoc-_view-view function viewDocsAsStream (ddoc, viewName, qs) { return view(ddoc, viewName, { type: 'view', stream: true }, qs) } // cloudant function viewSearch (ddoc, viewName, qs, callback) { return view(ddoc, viewName, { type: 'search' }, qs, callback) } // cloudant function viewSearchAsStream (ddoc, viewName, qs) { return view(ddoc, viewName, { type: 'search', stream: true }, qs) } // http://docs.couchdb.org/en/latest/api/ddoc/render.html#get--db-_design-ddoc-_show-func function showDoc (ddoc, viewName, docName, qs, callback) { if (missing(ddoc, viewName, docName)) { return callbackOrRejectError(callback) } return view(ddoc, viewName + '/' + docName, { type: 'show' }, qs, callback) } // http://docs.couchdb.org/en/latest/api/ddoc/render.html#put--db-_design-ddoc-_update-func-docid function updateWithHandler (ddoc, viewName, docName, body, callback) { if (typeof body === 'function') { callback = body body = undefined } if (missing(ddoc, viewName, docName)) { return callbackOrRejectError(callback) } return view(ddoc, viewName + '/' + encodeURIComponent(docName), { type: 'update', method: 'PUT', body }, callback) } function viewWithList (ddoc, viewName, listName, qs, callback) { return view(ddoc, listName + '/' + viewName, { type: 'list' }, qs, callback) } function viewWithListAsStream (ddoc, viewName, listName, qs, callback) { return view(ddoc, listName + '/' + viewName, { type: 'list', stream: true }, qs, callback) } // http://docs.couchdb.org/en/latest/api/database/bulk-api.html#post--db-_bulksDoc function bulksDoc (docs, qs0, callback0) { const { opts, callback } = getCallback(qs0, callback0) return relax({ db: dbName, path: '_bulk_docs', body: docs, method: 'POST', qs: opts }, callback) } // http://docs.couchdb.org/en/latest/api/document/common.html#creating-multiple-attachments function insertMultipart (doc, attachments, qs, callback) { if (typeof qs === 'string') { qs = { docName: qs } } qs = qs || {} const docName = qs.docName delete qs.docName if (missing(doc, attachments, docName)) { return callbackOrRejectError(callback) } doc = Object.assign({ _attachments: {} }, doc) const multipart = [] attachments.forEach(function (att) { doc._attachments[att.name] = { follows: true, length: Buffer.isBuffer(att.data) ? att.data.length : Buffer.byteLength(att.data), /* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */ content_type: att.content_type } multipart.push(att) }) multipart.unshift({ content_type: 'application/json', data: JSON.stringify(doc), name: 'document' }) return relax({ db: dbName, method: 'PUT', contentType: 'multipart/related', doc: docName, qs, multipart }, callback) } function getMultipart (docName, qs0, callback0) { const { opts, callback } = getCallback(qs0, callback0) opts.attachments = true if (missing(docName)) { return callbackOrRejectError(callback) } return relax({ db: dbName, doc: docName, encoding: null, accept: 'multipart/related', qs: opts }, callback) } function insertAtt (docName, attName, att, contentType, qs0, callback0) { const { opts, callback } = getCallback(qs0, callback0) if (missing(docName, attName, att, contentType)) { return callbackOrRejectError(callback) } return relax({ db: dbName, att: attName, method: 'PUT', contentType, doc: docName, qs: opts, body: att, dontStringify: true }, callback) } function getAtt (docName, attName, qs0, callback0) { const { opts, callback } = getCallback(qs0, callback0) if (missing(docName, attName)) { return callbackOrRejectError(callback) } return relax({ db: dbName, att: attName, doc: docName, qs: opts, encoding: null, dontParse: true }, callback) } function getAttAsStream (docName, attName, opts) { return relax({ db: dbName, att: attName, doc: docName, qs: opts, stream: true, encoding: null, dontParse: true }) } function destroyAtt (docName, attName, qs, callback) { if (missing(docName, attName)) { return callbackOrRejectError(callback) } return relax({ db: dbName, att: attName, method: 'DELETE', doc: docName, qs }, callback) } function find (query, callback) { if (missing(query) || typeof query !== 'object') { return callbackOrRejectError(callback) } return relax({ db: dbName, path: '_find', method: 'POST', body: query }, callback) } function findAsStream (query) { return relax({ db: dbName, path: '_find', method: 'POST', body: query, stream: true }) } function createIndex (indexDef, callback) { if (missing(indexDef) || typeof indexDef !== 'object') { return callbackOrRejectError(callback) } return relax({ db: dbName, path: '_index', method: 'POST', body: indexDef }, callback) } function partitionInfo (partitionKey, callback) { if (missing(partitionKey)) { return callbackOrRejectError(callback) } return relax({ db: dbName, path: '_partition/' + encodeURIComponent(partitionKey) }, callback) } function partitionedList (partitionKey, qs0, callback0) { const { opts, callback } = getCallback(qs0, callback0) if (missing(partitionKey)) { return callbackOrRejectError(callback) } return relax({ db: dbName, path: '_partition/' + encodeURIComponent(partitionKey) + '/_all_docs', qs: opts }, callback) } function partitionedListAsStream (partitionKey, qs) { return relax({ db: dbName, path: '_partition/' + encodeURIComponent(partitionKey) + '/_all_docs', qs, stream: true }) } function partitionedFind (partition, query, callback) { if (missing(partition, query) || typeof query !== 'object') { return callbackOrRejectError(callback) } return relax({ db: dbName, path: '_partition/' + encodeURIComponent(partition) + '/_find', method: 'POST', body: query }, callback) } function partitionedFindAsStream (partition, query) { return relax({ db: dbName, path: '_partition/' + encodeURIComponent(partition) + '/_find', method: 'POST', body: query, stream: true }) } function partitionedSearch (partition, ddoc, searchName, opts, callback) { if (missing(partition, ddoc, searchName, opts) || typeof opts !== 'object') { return callbackOrRejectError(callback) } return relax({ db: dbName, path: '_partition/' + encodeURIComponent(partition) + '/_design/' + ddoc + '/_search/' + searchName, qs: opts }, callback) } function partitionedSearchAsStream (partition, ddoc, searchName, opts) { return relax({ db: dbName, path: '_partition/' + encodeURIComponent(partition) + '/_design/' + ddoc + '/_search/' + searchName, qs: opts, stream: true }) } function partitionedView (partition, ddoc, viewName, opts, callback) { if (missing(partition, ddoc, viewName)) { return callbackOrRejectError(callback) } return relax({ db: dbName, path: '_partition/' + encodeURIComponent(partition) + '/_design/' + ddoc + '/_view/' + viewName, qs: opts }, callback) } function partitionedViewAsStream (partition, ddoc, viewName, opts) { return relax({ db: dbName, path: '_partition/' + encodeURIComponent(partition) + '/_design/' + ddoc + '/_view/' + viewName, qs: opts, stream: true }) } // db level exports docScope = { info: function (cb) { return getDb(dbName, cb) }, replicate: function (target, opts, cb) { return replicateDb(dbName, target, opts, cb) }, compact: function (cb) { return compactDb(dbName, cb) }, changes: function (qs, cb) { return changesDb(dbName, qs, cb) }, changesAsStream: function (qs) { return changesDbAsStream(dbName, qs) }, changesReader: new ChangesReader(dbName, relax), auth, session, insert: insertDoc, get: getDoc, head: headDoc, destroy: destroyDoc, bulk: bulksDoc, list: listDoc, listAsStream: listDocAsStream, fetch: fetchDocs, fetchRevs, config: { url: cfg.url, db: dbName }, multipart: { insert: insertMultipart, get: getMultipart }, attachment: { insert: insertAtt, get: getAtt, getAsStream: getAttAsStream, destroy: destroyAtt }, show: showDoc, atomic: updateWithHandler, updateWithHandler, baseView: view, search: viewSearch, searchAsStream: viewSearchAsStream, view: viewDocs, viewAsStream: viewDocsAsStream, find, findAsStream, createIndex, viewWithList, viewWithListAsStream, server: serverScope, replication: { enable: function (target, opts, cb) { return enableReplication(dbName, target, opts, cb) }, disable: function (id, revision, opts, cb) { return disableReplication(id, revision, opts, cb) }, query: function (id, opts, cb) { return queryReplication(id, opts, cb) } }, partitionInfo, partitionedList, partitionedListAsStream, partitionedFind, partitionedFindAsStream, partitionedSearch, partitionedSearchAsStream, partitionedView, partitionedViewAsStream } docScope.view.compact = function (ddoc, cb) { return compactDb(dbName, ddoc, cb) } return docScope } // server level exports serverScope = Object.assign(serverScope, { db: { create: createDb, get: getDb, destroy: destroyDb, list: listDbs, listAsStream: listDbsAsStream, use: docModule, scope: docModule, compact: compactDb, replicate: replicateDb, replication: { enable: enableReplication, disable: disableReplication, query: queryReplication }, changes: changesDb, updates }, use: docModule, scope: docModule, request: relax, relax, dinosaur: relax, auth, session, updates, uuids, info }) const db = maybeExtractDatabaseComponent() return db ? docModule(db) : serverScope } /* * and now an ascii dinosaur * _ * / _) ROAR! i'm a vegan! * .-^^^-/ / * __/ / * /__.|_|-|_| * * thanks for visiting! come again! */ function urlResolveFix (couchUrl, dbName) { if (/[^/]$/.test(couchUrl)) { couchUrl += '/' } return new URL(dbName, couchUrl).toString() }