marklogic
Version:
The official MarkLogic Node.js client API.
312 lines (277 loc) • 10.4 kB
JavaScript
/*
* Copyright (c) 2015-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
*/
;
const requester = require('./requester.js');
const mlutil = require('./mlutil.js');
const Operation = require('./operation.js');
/**
* Provides functions to execute resource services on the REST server
* for the client. The resource service extensions must have been
* installed previously on the REST server using the
* {@link config.resources#write} function.
* @namespace resources
*/
/** @ignore */
function checkArgs() {
if (arguments.length === 0) {
throw new Error('no argument for executing resource service');
}
const args = arguments[0];
if (args.name === void 0) {
throw new Error('no name for executing resource service');
}
return args;
}
/** @ignore */
function makeRequestOptions(client, args) {
let path = '/v1/resources/'+args.name;
let sep ='?';
const params = args.params;
const keys = (params === void 0) ? null : Object.keys(params);
const keyLen = (keys === null) ? 0 : keys.length;
let key = null;
let prefix = null;
let i=0;
let value = null;
let j=0;
for (; i < keyLen; i++) {
key = keys?keys[i]:null;
if(params){
value = params[key];
if (Array.isArray(value)) {
prefix = sep+encodeURIComponent('rs:'+key)+'=';
for (j=0; j < value.length; j++) {
path += prefix+encodeURIComponent(value[j]);
if (i === 0 && j === 0) {
sep ='&';
prefix = sep+encodeURIComponent('rs:'+key)+'=';
}
}
} else {
path += sep+'rs:'+key+'='+encodeURIComponent(value);
if (i === 0) {
sep ='&';
}
}
}
}
const txid = mlutil.convertTransaction(args.txid);
if (txid !== undefined && txid != null) {
path += sep+'txid='+mlutil.getTxidParam(txid);
if (sep === '?') {
sep ='&';
}
}
const requestOptions = mlutil.newRequestOptions(client.getConnectionParams(), path);
mlutil.addTxidHeaders(requestOptions, txid);
return requestOptions;
}
function validateStatusCode(statusCode) {
return (statusCode < 400) ? null : 'response with invalid '+statusCode+' status';
}
function Resources(client) {
if (!(this instanceof Resources)) {
return new Resources(client);
}
this.client = client;
}
//TODO: stream parameter should control whether object or chunked
/**
* Invokes the get() function in the resource service. The arguments
* must be passed as a single object with a property for each parameter.
* @method resources#get
* @since 1.0
* @param {string} name - the name of the service
* @param {object} [params] - an object in which each property has
* a variable name as a key and a number, string, or boolean value
* @param {string|transactions.Transaction} [txid] - a string
* transaction id or Transaction object identifying an open
* multi-statement transaction
* @returns {ResultProvider} an object whose stream() function returns
* a stream that receives the response
*/
Resources.prototype.get = function getResourceExec() {
return readResourceExec(
this, 'multipart', 'multipart/mixed; boundary='+mlutil.multipartBoundary,
checkArgs.apply(null, arguments)
);
};
Resources.prototype.getReadStream = function getResourceExecStream() {
const args = checkArgs.apply(null, arguments);
const contentType = args.contentType;
if (contentType == null) {
throw new Error('no content type for reading stream from resource service');
}
return readResourceExec(this, 'chunked', contentType, args);
};
/** @ignore */
function readResourceExec(self, responseType, contentType, args) {
const requestOptions = makeRequestOptions(self.client, args);
requestOptions.method = 'GET';
requestOptions.headers = {
'Accept': contentType
};
const operation = new Operation(
'execute remove service', self.client, requestOptions, 'empty', responseType
);
operation.name = args.name;
operation.statusCodeValidator = validateStatusCode;
return requester.startRequest(operation);
}
/**
* Invokes the post() function in the resource service. The arguments
* must be passed as a single object with a property for each parameter.
* @method resources#post
* @since 1.0
* @param {string} name - the name of the service
* @param {object} [params] - an object in which each property has
* a variable name as a key and a number, string, or boolean value
* @param {string|object|Buffer|ReadableStream} [documents] - any document
* content to send to the server
* @param {string|transactions.Transaction} [txid] - a string
* transaction id or Transaction object identifying an open
* multi-statement transaction
* @returns {ResultProvider} an object whose stream() function returns
* a stream that receives the response
*/
Resources.prototype.post = function postResourceExec() {
return writeResources(this, 'POST', 'multipart', checkArgs.apply(null, arguments));
};
/**
* Invokes the put() function in the resource service. The arguments
* must be passed as a single object with a property for each parameter.
* @method resources#put
* @since 1.0
* @param {string} name - the name of the service
* @param {object} [params] - an object in which each property has
* a variable name as a key and a number, string, or boolean value
* @param {string|object|Buffer|ReadableStream} [documents] - any document
* content to send to the server
* @param {string|transactions.Transaction} [txid] - a string
* transaction id or Transaction object identifying an open
* multi-statement transaction
*/
Resources.prototype.put = function putResourceExec() {
return writeResources(this, 'PUT', 'single', checkArgs.apply(null, arguments));
};
Resources.prototype.postWriteStream = function postResourceExecStream() {
return writeResourceStream(this, 'POST', 'multipart', checkArgs.apply(null, arguments));
};
Resources.prototype.putWriteStream = function putResourceExecStream() {
return writeResourceStream(this, 'PUT', 'single', checkArgs.apply(null, arguments));
};
/** @ignore */
function writeResourceStream(self, method, responseType, args) {
const contentType = args.contentType;
if (contentType == null) {
throw new Error('no content type for writing stream to resource service');
}
const requestOptions = makeRequestOptions(self.client, args);
requestOptions.headers = {
'Content-Type': contentType,
'Accept': 'application/json'
};
requestOptions.method = method;
const operation = new Operation(
'execute '+method+' service stream', self.client, requestOptions,
'chunked', responseType
);
operation.name = args.name;
operation.isReplayable = false;
operation.statusCodeValidator = validateStatusCode;
return requester.startRequest(operation);
}
/** @ignore */
function writeResources(self, method, responseType, args) {
let documents = args.documents;
const isEmpty = (documents == null);
if (!isEmpty && !Array.isArray(documents)) {
documents = [documents];
}
const multipartBoundary = mlutil.multipartBoundary;
const requestOptions = makeRequestOptions(self.client, args);
requestOptions.method = method;
requestOptions.headers = (!isEmpty) ?
{
'Content-Type': 'multipart/mixed; boundary='+multipartBoundary,
'Accept': 'application/json'
} :
{
'Accept': 'application/json'
};
const operation = new Operation(
'execute '+method+' service', self.client, requestOptions,
(isEmpty ? 'empty' : 'multipart'), responseType
);
operation.name = args.name;
if (!isEmpty) {
operation.multipartBoundary = multipartBoundary;
}
operation.requestPartList = [];
if (typeof documents !== 'undefined' && documents !== null) {
for (let i=0; i < documents.length; i++) {
addPart(operation, documents[i]);
}
}
operation.statusCodeValidator = validateStatusCode;
return requester.startRequest(operation);
}
/** @ignore */
function addPart(operation, document) {
const headers = {};
const part = {
headers: headers
};
const content = document.content;
const hasContent = (content != null);
const contentType = hasContent ? document.contentType : null;
if (hasContent && (contentType != null)) {
const marshaledData = mlutil.marshal(content, operation);
/* TODO: allow encoding in multipart parse
headers['Content-Type'] = contentType +
((typeof marshaledData === 'string' || marshaledData instanceof String) ? '; charset=utf-8' : '');
*/
headers['Content-Type'] = contentType;
part.content = marshaledData;
} else if (typeof document === 'string' || document instanceof String) {
part.content = document;
// headers['Content-Type'] = 'text/plain; charset=utf-8';
headers['Content-Type'] = 'text/plain';
} else if (Buffer.isBuffer(document)) {
part.content = document;
headers['Content-Type'] = 'application/x-unknown-content-type';
} else {
part.content = JSON.stringify(document);
// headers['Content-Type'] = 'application/json; charset=utf-8';
headers['Content-Type'] = 'application/json';
}
operation.requestPartList.push(part);
}
/**
* Invokes the delete() function in the resource service. The arguments
* must be passed as a single object with a property for each parameter.
* @method resources#remove
* @since 1.0
* @param {string} name - the name of the service
* @param {object} [params] - an object in which each property has
* a variable name as a key and a number, string, or boolean value
* @param {string|transactions.Transaction} [txid] - a string
* transaction id or Transaction object identifying an open
* multi-statement transaction
*/
Resources.prototype.remove = function removeResourceExec() {
const args = checkArgs.apply(null, arguments);
const requestOptions = makeRequestOptions(this.client, args);
requestOptions.headers = {
'Accept': 'application/json'
};
requestOptions.method = 'DELETE';
const operation = new Operation(
'execute remove service', this.client, requestOptions, 'empty', 'single'
);
operation.name = args.name;
operation.statusCodeValidator = validateStatusCode;
return requester.startRequest(operation);
};
module.exports = Resources;