marklogic
Version:
The official MarkLogic Node.js client API.
521 lines (476 loc) • 14.4 kB
JavaScript
/*
* Copyright (c) 2015-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
*/
;
const util = require('util');
const multipartBoundary = 'MLBOUND_' + Date.UTC(2014,12,31);
// Normalize arguments by returning them as an array.
function asArray() {
const argLen = arguments.length;
switch(argLen) {
// No arguments returns an empty array
case 0:
return [];
// Single array argument returns that array
case 1:
var arg = arguments[0];
if (Array.isArray(arg)) {
return arg;
}
// Single object argument returns an array with object as only element
return [arg];
// List of arguments returns an array with arguments as elements
default:
var args = new Array(argLen);
for(let i=0; i < argLen; ++i) {
args[i] = arguments[i];
}
return args;
}
}
function copyProperties(source, target, srcKeys) {
const dest = (arguments.length > 1) ? target : {};
const isNamed = Array.isArray(srcKeys);
const keys = isNamed ? srcKeys : Object.keys(source);
// for...in not currently optimized by v8
const keyLen = keys.length;
for (let i=0; i < keyLen; i++) {
const key = keys[i];
const val = source[key];
if (!isNamed || (val !== void 0 && val !== null)) {
dest[key] = val;
}
}
return dest;
}
function first() {
switch(arguments.length) {
case 0:
return {};
default:
var firstArg = arguments[0];
if (firstArg instanceof Array) {
return firstArg[0];
}
return firstArg;
}
}
function appendItem(object, key, value) {
if (value === void 0) {
return;
}
const array = object[key];
if (array === void 0) {
object[key] = [value];
} else {
array.push(value);
}
}
// isolated in function because v8 deoptimizes try/catch
function parseJSON(raw) {
try {
return JSON.parse(raw);
} catch(e) {
// TODO: debug logging
return raw;
}
}
function MarkLogicError(firstArg, secondArg) {
if (!(this instanceof MarkLogicError)) {
return new MarkLogicError(firstArg, secondArg);
}
let name = null;
let message = null;
if (firstArg == null) {
message = 'unknown error';
} else if (secondArg == null) {
message = firstArg;
} else {
name = firstArg;
message = secondArg;
}
Error.call(this);
if (name !== null) {
this.name = name;
}
this.message = message;
// Add stack trace to this.stack
Error.captureStackTrace(this, MarkLogicError);
}
util.inherits(MarkLogicError, Error);
function callbackOn(object, method) {
const self = object;
const func = method;
return function callBackMethod() {
return func.apply(self, arguments);
};
}
function endpointTransform(transform) {
if (transform != null) {
if (Array.isArray(transform)) {
switch(transform.length) {
case 0:
break;
case 1:
return 'transform='+encodeURIComponent(transform[0]);
default:
var endpointParam = 'transform='+encodeURIComponent(transform[0]);
var transformParams = transform[1];
var transformKeys = Object.keys(transformParams);
for (let i=0; i < transformKeys.length; i++) {
const transformKey = transformKeys[i];
endpointParam += '&trans:'+encodeURIComponent(transformKey)+'='+
encodeURIComponent(transformParams[transformKey]);
}
return endpointParam;
}
} else {
return 'transform='+encodeURIComponent(transform);
}
}
}
function newRequestOptions(connectionParams, endpoint, method) {
const requestOptions = copyProperties(connectionParams);
const database = connectionParams.database;
if(connectionParams.basePath) {
const basePath = connectionParams.basePath.toString();
let fixedBasePath = (connectionParams.host.toString().endsWith('/') && basePath.startsWith('/')) ? basePath.substring(1):basePath;
fixedBasePath = fixedBasePath.endsWith('/') ? fixedBasePath.substring(0,fixedBasePath.length-1):fixedBasePath;
endpoint = fixedBasePath+endpoint;
}
requestOptions.method = method? method:'GET';
if (database !== null) {
if (endpoint.includes('?')) {
if (endpoint.endsWith('?')) {
requestOptions.path = (database == null) ? endpoint : (endpoint + 'database=' + encodeURIComponent(database));
} else {
requestOptions.path = (database == null) ? endpoint : (endpoint + '&database=' + encodeURIComponent(database));
}
} else {
requestOptions.path = (database == null) ? endpoint : (endpoint + '?database=' + encodeURIComponent(database));
}
}
return requestOptions;
}
function extension(filename) {
const extStart = filename.lastIndexOf('.') + 1;
if (extStart === 0 || extStart === filename.length) {
return null;
}
return filename.substring(extStart);
}
function rootname(filename) {
const extStart = filename.lastIndexOf('.');
if (extStart === 0 || extStart === filename.length) {
return null;
}
return filename.substring(0,extStart);
}
function identify(arg, withValues) {
if (arg === void 0) {
return 'undefined';
}
if (arg === null) {
return 'null';
}
const typed = typeof arg;
switch(typed) {
case 'boolean' : return withValues ? typed+' '+arg : typed;
case 'function' : return typed;
case 'number' : return withValues ? typed+' '+arg : typed;
case 'object' :
if (Array.isArray(arg)) {
return withValues ? 'Array '+JSON.stringify(arg) : 'Array';
}
if (Buffer.isBuffer(arg)) {
return 'Buffer';
}
if (arg instanceof Error) {
return withValues ? 'Error '+JSON.stringify(arg) : 'Error';
}
var prototypeName = Object.prototype.toString.call(arg);
var objectType = prototypeName.replace(/^\[object ([^\]])$/, '$1');
if ((objectType != null) && objectType !== prototypeName) {
return withValues ? objectType+' '+JSON.stringify(arg) : objectType;
}
return withValues ? typed+' '+JSON.stringify(arg) : typed;
case 'string' : return withValues ? typed+' '+arg : typed;
case 'symbol' : return withValues ? typed+' '+arg : typed;
default : return withValues ? typed+' '+arg : typed;
}
}
function marshal(data, state) {
if (data == null) {
return null;
} else if (typeof data === 'string' || data instanceof String) {
return data;
} else if (Buffer.isBuffer(data)) {
return data;
// readable stream might not inherit from ReadableStream
} else if (typeof data._read === 'function') {
if (state !== void 0 && state.isReplayable === true) {
state.isReplayable = false;
}
return data;
} else if (typeof data === 'object' && data !== null) {
if (Array.isArray(data)) {
return JSON.stringify(data);
} else if (data instanceof Date) {
return data.toISOString();
} else if (data instanceof Set) {
return JSON.stringify(Array.from(data));
} else if (data instanceof Map) {
const obj = {};
data.forEach((value, key) => {obj[key] = value;});
return JSON.stringify(obj);
}
return JSON.stringify(data);
}
return String(data);
}
function unmarshal(format, data) {
if (data == null) {
return null;
}
// TODO: readable stream
switch(format) {
case 'binary':
return data;
case 'json':
if (data.length === 0) {
return data;
} else if (typeof data === 'string' || data instanceof String) {
return parseJSON(data);
} else if (Buffer.isBuffer(data)) {
return parseJSON(data.toString());
}
return data;
case 'text':
case 'xml':
if (Buffer.isBuffer(data)) {
return data.toString();
}
return data;
default:
return data;
}
}
function convertTransaction(transaction) {
if (transaction === null || transaction === void 0) {
return null;
} else if (typeof transaction === 'string' || transaction instanceof String || transaction instanceof Transaction) {
return transaction;
} else if (transaction.txid !=null && transaction.cookies!=null) {
return Transaction(transaction.txid, transaction.cookies);
} else {
throw new Error(
'cannot process transaction without string or Transaction or Transaction properties'
);
}
}
function Transaction(id, cookies) {
if (!(this instanceof Transaction)) {
return new Transaction(id, cookies);
}
this.txid = id;
if (Array.isArray(cookies) && cookies.length > 0) {
for (let i=0; i < cookies.length; i++) {
cookies[i] = cookies[i].replace(/;\s*expires\s*=[^;]+(;|$)/i, '$1');
}
this.cookies = cookies;
}
}
Transaction.prototype.toString = function transactionToString() {
return this.txid;
};
function getTxidParam(txid, action) {
if (txid == null) {
if (action != null) {
throw new Error(
'cannot '+action+' transaction without string or Transaction object identifier'
);
}
return;
}
if (typeof txid === 'string' || txid instanceof String) {
return txid;
} else if (txid instanceof Transaction) {
return txid.txid;
}
throw new Error(
'can only '+action+' transaction with string or Transaction object identifier: '+
(typeof txid)
);
}
function addTxidHeaders(requestOptions, txid) {
if (txid instanceof Transaction) {
const cookies = txid.cookies;
if (cookies != null) {
const headers = requestOptions.headers;
if (headers != null) {
headers.cookie = cookies;
} else {
requestOptions.headers = {
cookie: cookies
};
}
}
}
}
function Timestamp(value) {
if (!(this instanceof Timestamp)) {
return new Timestamp();
}
this.value = null;
if (value !== undefined && value !== null) {
if (typeof value === 'string' || value instanceof String) {
this.value = value;
} else {
throw new Error('timestamp value must be specified as a string');
}
}
}
// Slice mode can be 'array' or 'legacy'
let sliceMode = 'array';
function setSliceMode(mode) {
sliceMode = mode;
}
function makeSliceClause(variant, args) {
const argLen = args.length;
const sliceClause = {};
let firstArg = null;
let secondArg = null;
const argMax = Math.min(argLen, ((variant === 'query') ? 5 : 3));
let arg = null;
for (let i=0; i < argMax; i++) {
arg = args[i];
if (typeof arg === 'number' || arg instanceof Number) {
switch(i) {
case 0:
firstArg = arg;
break;
case 1:
secondArg = arg;
break;
}
} else {
switch(variant) {
case 'query':
if (arg['transform-results'] !== void 0) {
sliceClause['transform-results'] = arg['transform-results'];
continue;
} else if (arg['extract-document-data'] !== void 0) {
sliceClause['extract-document-data'] = arg['extract-document-data'];
continue;
} else if (arg['document-transform'] !== void 0) {
sliceClause['document-transform'] = arg['document-transform'];
continue;
}
break;
case 'values':
if (arg['document-transform'] !== void 0) {
sliceClause['document-transform'] = arg['document-transform'];
continue;
}
break;
}
throw new Error('unknown slice argument: '+identify(arg, true));
}
}
if (firstArg !== null && secondArg !== 0) {
const pageStart = (sliceMode === 'legacy') ? firstArg : firstArg + 1;
if (pageStart === 0 && secondArg === null) {
sliceClause['page-length'] = 0;
} else {
sliceClause['page-start'] = pageStart ;
}
}
if (secondArg !== null) {
if (sliceMode === 'legacy') {
sliceClause['page-length'] = secondArg;
} else {
sliceClause['page-length'] = secondArg - firstArg;
}
}
return sliceClause;
}
function makeBindingsParams(bindings, sep) {
const keys = Object.keys(bindings);
const max = keys.length;
let paramStr = '';
for (let i = 0; i < max; i++) {
const key = keys[i];
let objs = bindings[key];
if (!Array.isArray(objs)) {
objs = [objs];
}
const nextParams = objs.map(obj => {
let value = obj.value;
let name = null;
if (value === void 0) {
name = key;
value = obj;
} else {
const type = obj.type;
const lang = obj.lang;
const hasType = (type !== null && type !== void 0);
if (hasType) {
if (typeof type !== 'string' && !(type instanceof String)) {
throw new Error('type must be string');
}
if (type.indexOf(':') > -1) {
throw new Error('type cannot contain colon - '+type);
}
}
const hasLang = (lang !== null && lang !== void 0);
if (hasLang) {
if (typeof lang !== 'string' && !(lang instanceof String)) {
throw new Error('lang must be string');
}
}
if (hasType && hasLang && type !== 'string') {
throw new Error('cannot combine type with lang - '+type+' '+lang);
} else if (hasLang) {
name = key + '@' + lang;
} else if (hasType) {
name = key + ':' + type;
} else {
name = key;
}
}
return encodeURIComponent('bind:'+name)+'='+encodeURIComponent(value);
}).join('&');
if (nextParams.length !== 0) {
paramStr += sep+nextParams;
if (sep === '?') {
sep = '&';
}
}
}
return paramStr;
}
module.exports = {
addTxidHeaders: addTxidHeaders,
appendItem: appendItem,
asArray: asArray,
callbackOn: callbackOn,
convertTransaction: convertTransaction,
copyProperties: copyProperties,
newRequestOptions: newRequestOptions,
endpointTransform: endpointTransform,
identify: identify,
Error: MarkLogicError,
extension: extension,
first: first,
getTxidParam: getTxidParam,
makeSliceClause: makeSliceClause,
makeBindingsParams: makeBindingsParams,
marshal: marshal,
multipartBoundary: multipartBoundary,
parseJSON: parseJSON,
rootname: rootname,
setSliceMode: setSliceMode,
Timestamp: Timestamp,
Transaction: Transaction,
unmarshal: unmarshal
};