UNPKG

marklogic

Version:

The official MarkLogic Node.js client API.

939 lines (883 loc) 28.6 kB
/* * Copyright © 2015-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved. */ 'use strict'; var mlutil = require('./mlutil.js'); var qb = require('./query-builder.js'); /** * A helper for building the definition of a document patch. The helper is * created by the {@link marklogic.patchBuilder} function. * @namespace patchBuilder */ /** * An operation as part of a document patch request. * @typedef {object} patchBuilder.PatchOperation * @since 1.0 */ /** * Builds an operation to remove a JSON property or XML element or attribute. * @method * @since 1.0 * @memberof patchBuilder# * @param {string} select - the path to select the fragment to remove * @param {string} [cardinality] - a specification from the ?|.|*|+ * enumeration controlling whether the select path must match zero-or-one * fragment, exactly one fragment, any number of fragments (the default), or * one-or-more fragments. * @returns {patchBuilder.PatchOperation} a patch operation */ function remove() { var select = null; var cardinality = null; var argLen = arguments.length; for (var i=0; i < argLen; i++) { var arg = arguments[i]; if (i === 0) { select = arg; continue; } if (cardinality === null && /^[?.*+]$/.test(arg)) { cardinality = arg; continue; } break; } if (select === null) { throw new Error('remove takes select and optional cardinality'); } var operation = { select: select }; if (cardinality !== null) { operation.cardinality = cardinality; } return {'delete': operation}; } /** * Builds an operation to insert content. * @method * @since 1.0 * @memberof patchBuilder# * @param {string} context - the path to the container of the inserted content * @param {string} position - a specification from the before|after|last-child * enumeration controlling where the content will be inserted relative to the context * @param content - the inserted object or value * @param {string} [cardinality] - a specification from the ?|.|*|+ * enumeration controlling whether the context path must match zero-or-one * fragment, exactly one fragment, any number of fragments (the default), or * one-or-more fragments. * @returns {patchBuilder.PatchOperation} a patch operation */ function insert() { var context = null; var position = null; var content = void 0; var cardinality = null; var argLen = arguments.length; for (var i=0; i < argLen; i++) { var arg = arguments[i]; if (i === 0) { context = arg; continue; } if (arg === null && content === void 0) { content = arg; continue; } var isString = (typeof arg === 'string' || arg instanceof String); if (isString) { if (position === null && /^(before|after|last-child)$/.test(arg)) { position = arg; continue; } if (cardinality === null && /^[?.*+]$/.test(arg)) { cardinality = arg; continue; } } if (content === void 0) { content = arg; continue; } break; } if (context === null || position === null || content === void 0) { throw new Error( 'insert takes context, position, content, and optional cardinality' ); } var operation = { context: context, position: position, content: content }; if (cardinality !== null) { operation.cardinality = cardinality; } return {insert: operation}; } /** * The specification for a library of replacement functions as returned by * the {@link patchBuilder#library} function. * @typedef {object} patchBuilder.LibraryParam * @since 1.0 */ /** * Specifies a library supplying functions to apply to existing content * to produce the replacement content as part of * {@link patchBuilder#replace} or {@link patchBuilder#replaceInsert} operations. * The library must be installed as /ext/marklogic/patch/apply/MODULE_NAME.xqy * or /ext/marklogic/patch/apply/MODULE_NAME.sjs and must have the * http://marklogic.com/patch/apply/MODULE_NAME namespace if the library has .xqy extension. * @method * @since 1.0 * @memberof patchBuilder# * @param {string} moduleName - the name of the module with the functions * @returns {patchBuilder.LibraryParam} the specification for applying a function */ function library(module) { if (module == null) { throw new Error( 'library must name the module that defines the apply functions' ); } var rootname = mlutil.rootname(module); if (rootname === null) { throw new Error('library must have an extension of .sjs or .xqy'); } var extension = module.substring((module.lastIndexOf('.')+1), module.length); if(extension === "sjs") { return {'replace-library':{ at: '/ext/marklogic/patch/apply/'+module }}; } return {'replace-library':{ ns: 'http://marklogic.com/patch/apply/'+rootname, at: '/ext/marklogic/patch/apply/'+module }}; } /** * The specification for applying a function to produce the content for a * {@link patchBuilder#replace} or {@link patchBuilder#replaceInsert} * operation. * @typedef {object} patchBuilder.ApplyDefinition * @since 1.0 */ /** * Specifies a function to apply to produce the replacement or insertion * content for a {@link patchBuilder#replace} or {@link patchBuilder#replaceInsert} * operation. * @method * @since 1.0 * @memberof patchBuilder# * @param {string} functionName - the name of the function to apply * @param ...args - arguments to pass to the applied function; you can use the * datatype() function to specify an atomic type in the list prior to a value * @returns {patchBuilder.ApplyDefinition} the specification for applying a function */ function apply() { var args = mlutil.asArray.apply(null, arguments); var argLen = args.length; switch(argLen) { case 0: throw new Error('no name for function to apply'); case 1: return {apply: args[0]}; default: var DatatypeDef = qb.lib.DatatypeDef; var functionName = args[0]; var content = []; var datatype = null; var arg = null; var i=1; for (; i < argLen; i++) { arg = args[i]; if (arg instanceof DatatypeDef) { if (datatype !== null) { throw new Error(datatype+' datatype without following value for '+functionName); } datatype = arg.datatype; continue; } if (datatype !== null) { content.push({$value: arg, $datatype: datatype}); datatype = null; } else if (Object.prototype.toString.call(arg) === '[object Date]') { content.push({$value: arg.toISOString(), $datatype: 'xs:datetime'}); } else { content.push({$value: arg}); } } if (datatype !== null) { throw new Error(datatype+' datatype without last value for '+functionName); } return {apply: functionName, content: content}; } } /** * Adds a number to the existing value to produce the replace content * for a {@link patchBuilder#replace} operation. * @method * @since 1.0 * @memberof patchBuilder# * @param {number} number - the number to add * @returns {patchBuilder.ApplyDefinition} the specification for applying a function */ function add(number) { if (arguments.length < 1) { throw new Error('no number to add'); } return apply('ml.add', number); } /** * Subtracts a number from the existing value to produce the replace content * for a {@link patchBuilder#replace} operation. * @method * @since 1.0 * @memberof patchBuilder# * @param {number} number - the number to subtract * @returns {patchBuilder.ApplyDefinition} the specification for applying a function */ function subtract(number) { if (arguments.length < 1) { throw new Error('no number to subtract'); } return apply('ml.subtract', number); } /** * Multiplies the existing value by a number to produce the replace content * for a {@link patchBuilder#replace} operation. * @method * @since 1.0 * @memberof patchBuilder# * @param {number} multiplier - the number to multiply by * @returns {patchBuilder.ApplyDefinition} the specification for applying a function */ function multiplyBy(multiplier) { if (arguments.length < 1) { throw new Error('no number to multiply by'); } return apply('ml.multiply', multiplier); } /** * Divides the existing by a number to produce the replace content * for a {@link patchBuilder#replace} operation. * @method * @since 1.0 * @memberof patchBuilder# * @param {number} divisor - the number to divide by * @returns {patchBuilder.ApplyDefinition} the specification for applying a function */ function divideBy(divisor) { if (arguments.length < 1) { throw new Error('no number to divide by'); } return apply('ml.divide', divisor); } /** * Prepends a value to the existing value for a {@link patchBuilder#replace} operation. * @method * @since 1.0 * @memberof patchBuilder# * @param {string} prepended - the string to prepend * @returns {patchBuilder.ApplyDefinition} the specification for applying a function */ function concatBefore(prepended) { if (arguments.length < 1) { throw new Error('no string to concat before'); } return apply('ml.concat-before', prepended); } /** * Appends a value to the existing value for a {@link patchBuilder#replace} operation. * @method * @since 1.0 * @memberof patchBuilder# * @param {string} appended - the string to append * @returns {patchBuilder.ApplyDefinition} the specification for applying a function */ function concatAfter(appended) { if (arguments.length < 1) { throw new Error('no string to concat after'); } return apply('ml.concat-after', appended); } /** * Prepends and appends values to the existing value for * a {@link patchBuilder#replace} operation. * @method * @since 1.0 * @memberof patchBuilder# * @param {string} prepended - the string to prepend * @param {string} appended - the string to append * @returns {patchBuilder.ApplyDefinition} the specification for applying a function */ function concatBetween(prepended, appended) { if (arguments.length < 2) { throw new Error('no strings to concat before and after'); } return apply('ml.concat-between', prepended, appended); } /** * Trims a leading substring from the existing value for * a {@link patchBuilder#replace} operation. * @method * @since 1.0 * @memberof patchBuilder# * @param {string} start - the leading string to trim * @returns {patchBuilder.ApplyDefinition} the specification for applying a function */ function substringAfter(start) { if (arguments.length < 1) { throw new Error('no substring for after'); } return apply('ml.substring-after', start); } /** * Trims a trailing substring from the existing value for * a {@link patchBuilder#replace} operation. * @method * @since 1.0 * @memberof patchBuilder# * @param {string} end - the trailing string to trim * @returns {patchBuilder.ApplyDefinition} the specification for applying a function */ function substringBefore(end) { if (arguments.length < 1) { throw new Error('no substring for before'); } return apply('ml.substring-before', end); } /** * Applies a regular expression to the existing value to produce a new value for * a {@link patchBuilder#replace} operation. * @method * @since 1.0 * @memberof patchBuilder# * @param {string} match - the expression extracting parts of the existing value * @param {string} end - the expression to assembling the extracted parts into * a replacement value * @param {string} [flags] - the flags changing the regex operation * @returns {patchBuilder.ApplyDefinition} the specification for applying a function */ function replaceRegex(match, replace, flags) { if (arguments.length < 2) { throw new Error('no match and replace for replace regex'); } return (flags === void 0) ? apply('ml.replace-regex', match, replace) : apply('ml.replace-regex', match, replace, flags); } /** * Builds an operation to replace a JSON property or XML element or attribute. * @method * @since 1.0 * @memberof patchBuilder# * @param {string} select - the path to select the fragment to replace * @param [content] - the object or value replacing the selected fragment or * an {@link patchBuilder.ApplyDefinition} specifying a function to apply to the selected * fragment to produce the replacement * @param {string} [cardinality] - a specification from the ?|.|*|+ * enumeration controlling whether the select path must match zero-or-one * fragment, exactly one fragment, any number of fragments (the default), or * one-or-more fragments. * @returns {patchBuilder.PatchOperation} a patch operation */ function replace() { var select = null; var content = void 0; var cardinality = null; var apply = null; var argLen = arguments.length; for (var i=0; i < argLen; i++) { var arg = arguments[i]; if (i === 0) { select = arg; continue; } if (arg === null && content === void 0) { content = arg; continue; } var isString = (typeof arg === 'string' || arg instanceof String); if (isString && cardinality === null && /^[?.*+]$/.test(arg)) { cardinality = arg; continue; } if (apply === null || apply === undefined) { if(arg){ apply = arg.apply; } if (apply != null) { content = arg.content; continue; } } if (content === void 0) { content = arg; continue; } break; } if (select === null) { throw new Error( 'replace takes a select path, content or an apply function, and optional cardinality' ); } var operation = { select: select, content: content }; if (cardinality != null) { operation.cardinality = cardinality; } if (apply != null) { operation.apply = apply; } return {replace: operation}; } /** * Builds an operation to replace a fragment if the fragment exists and insert * the new content if the fragment doesn't exist. * The content argument is optional if an apply argument is provided and * required otherwise. * @method * @since 1.0 * @memberof patchBuilder# * @param {string} select - the path to select the fragment to replace * @param {string} context - the path to the container for inserting the content * when the select path doesn't match * @param {string} position - a specification from the before|after|last-child * enumeration controlling where the content will be inserted relative to the context * @param [content] - the object or value replacing the selected fragment or, * alternatively, inserting within the context or * an {@link patchBuilder.ApplyDefinition} specifying a function to generate the * replacement for the selected fragment or the inserted content * @param {string} [cardinality] - a specification from the ?|.|*|+ * enumeration controlling whether the select or context path must match zero-or-one * fragment, exactly one fragment, any number of fragments (the default), or * one-or-more fragments. * @returns {patchBuilder.PatchOperation} a patch operation */ function replaceInsert() { var select = null; var context = null; var position = null; var content = void 0; var cardinality = null; var apply = null; var argLen = arguments.length; for (var i=0; i < argLen; i++) { var arg = arguments[i]; if (i === 0) { select = arg; continue; } if (i === 1) { context = arg; continue; } if (arg === null && content === void 0) { content = arg; continue; } var isString = (typeof arg === 'string' || arg instanceof String); if (isString) { if (position === null && /^(before|after|last-child)$/.test(arg)) { position = arg; continue; } if (cardinality === null && /^[?.*+]$/.test(arg)) { cardinality = arg; continue; } } if (arg && !apply) { apply = arg.apply; if (apply != null) { content = arg.content; continue; } } if (content === void 0) { content = arg; continue; } break; } if (select=== null || context === null || position === null) { throw new Error( 'replaceInsert takes select, context, position, content or an apply function, '+ 'and optional cardinality' ); } var operation = { select: select, context: context, position: position, content: content }; if (cardinality != null) { operation.cardinality = cardinality; } if (apply != null) { operation.apply = apply; } return {'replace-insert': operation}; } /** * The specification for whether select and context paths use * XPath or JSONPath as returned by * the {@link patchBuilder#pathLanguage} function. * @typedef {object} patchBuilder.PathLanguageParam * @since 1.0 */ /** * Specifies whether the language used in context and select paths * for the document patch is XPath or JSONPath. XPath may be used * for either JSON or XML documents and is the default path language. * JSONPath may only be used for JSON documents. A document patch * cannot contain a mix of XPath and JSONPath. * @method * @since 1.0 * @memberof patchBuilder# * @param {string} language - one of the enumeration xpath|jsonpath * @returns {patchBuilder.PathLanguageParam} the specification for the path language */ function pathLanguage() { var pathlang = (arguments.length < 1) ? null : arguments[0]; if (pathlang !== 'jsonpath' && pathlang !== 'xpath') { throw new Error( 'pathLanguage takes a path language of xpath or jsonpath' ); } return {"pathlang": pathlang}; } /** * Specifies operations to patch the collections of a document. * @namespace patchBuilderCollections */ /** * Specifies a collection to add to a document's metadata. * @method patchBuilderCollections#add * @since 1.0 * @param {string} collection - the name of the collection * @returns {patchBuilder.PatchOperation} a patch operation */ function addCollection(collection) { if (typeof collection !== 'string') { throw new Error('collections.add() takes a string name'); } return insert('/array-node("collections")', 'last-child', collection); } /** * Specifies a collection to remove from a document's metadata. * @method patchBuilderCollections#remove * @since 1.0 * @param {string} collection - the name of the collection * @returns {patchBuilder.PatchOperation} a patch operation */ function removeCollection(collection) { if (typeof collection !== 'string') { throw new Error('collections.remove() takes a string name'); } return remove('/collections[. eq "'+collection+'"]'); } /** * Specifies operations to patch the permissions of a document. * @namespace patchBuilderPermissions */ /** * Specifies a role to add to a document's permissions; takes a configuration * object with the following named parameters or, as a shortcut, * a role string and capabilities string or array. * @method patchBuilderPermissions#add * @since 1.0 * @param {string} role - the name of a role defined in the server configuration * @param {string|string[]} capabilities - the capability or an array of * capabilities from the insert|update|read|execute enumeration * @returns {patchBuilder.PatchOperation} a patch operation */ function addPermission() { var permission = getPermission( mlutil.asArray.apply(null, arguments) ); if (permission === null) { throw new Error('permissions.add() takes the role name and one or more insert|update|read|execute capabilities'); } return insert('/array-node("permissions")', 'last-child', permission); } /** * Specifies different capabilities for a role with permissions on a document; * takes a configuration object with the following named parameters or, * as a shortcut, a role string and capabilities string or array. * @method patchBuilderPermissions#replace * @since 1.0 * @param {string} role - the name of an existing role with permissions * on the document * @param {string|string[]} capabilities - the role's modified capability or * capabilities from the insert|update|read|execute enumeration */ function replacePermission() { var permission = getPermission( mlutil.asArray.apply(null, arguments) ); if (permission === null) { throw new Error('permissions.replace() takes the role name and one or more insert|update|read|execute capabilities'); } return replace( '/permissions[node("role-name") eq "'+permission['role-name']+'"]', permission ); } /** * Specifies a role to remove from a document's permissions. * @method patchBuilderPermissions#remove * @since 1.0 * @param {string} role - the role to remove from access to the document * @returns {patchBuilder.PatchOperation} a patch operation */ function removePermission(roleName) { if (typeof roleName !== 'string') { throw new Error('permissions.remove() takes a string role name'); } return remove('/permissions[node("role-name") eq "'+roleName+'"]'); } /** @ignore */ function getPermission(args) { var argLen = args.length; var roleName = null; var capabilities = null; var isObject = false; var first = (argLen === 0) ? null : args[0]; if (first !== null) { if (args.length > 1 && typeof first === 'string') { roleName = first; capabilities = args[1]; } else { roleName = first['role-name']; if (typeof roleName !== 'string') { return null; } capabilities = first.capabilities; isObject = true; } } if (capabilities == null) { return null; } var check = {execute:true, insert:true, read:true, update:true}; if (Array.isArray(capabilities)) { var max = capabilities.length; for (var i=0; i < max; i++) { if (!check[capabilities[i]]) { return null; } } } else { if (!check[capabilities]) { return null; } capabilities = [capabilities]; if (isObject) { isObject = false; } } return isObject ? first : { 'role-name': roleName, capabilities: capabilities }; } /** * Specifies operations to patch the metadata properties of a document. * @namespace patchBuilderProperties */ /** * Specifies a new property to add to a document's metadata. * @method patchBuilderProperties#add * @since 1.0 * @param {string} name - the name of the new metadata property * @param value - the value of the new metadata property * @returns {patchBuilder.PatchOperation} a patch operation */ function addProperty(name, value) { if (typeof name !== 'string' || value == null) { throw new Error('properties.add() takes a string name and a value'); } var prop = {}; prop[name] = value; return insert('/object-node("properties")', 'last-child', prop); } /** * Specifies a different value for a property in a document's metadata. * @method patchBuilderProperties#replace * @since 1.0 * @param {string} name - the name of the existing metadata property * @param value - the modified value of the metadata property * @returns {patchBuilder.PatchOperation} a patch operation */ function replaceProperty(name, value) { if (typeof name !== 'string' || value == null) { throw new Error('properties.replace() takes a string name and a value'); } return replace('/properties/node("'+name+'")', value); } /** * Specifies a metadata property to remove from the document's metadata. * @method patchBuilderProperties#remove * @since 1.0 * @param {string} name - the name of the metadata property to remove * @returns {patchBuilder.PatchOperation} a patch operation */ function removeProperty(name) { if (typeof name !== 'string') { throw new Error('properties.remove() takes a string property name'); } return remove('/properties/node("'+name+'")'); } /** * Specifies operations to patch the search quality of a document. * @namespace patchBuilderQuality */ /** * Sets the search quality of a document. * @method patchBuilderQuality#set * @since 1.0 * @param {number} quality - the numeric value of the quality */ function setQuality(quality) { if (typeof quality !== 'number') { throw new Error('quality.set() takes a number'); } return replace('/quality', quality); } /** * Specifies operations to patch the metadata values of a document. * @namespace patchBuilderMetadataValues * @since 2.0.1 */ /** * Specifies a new metadata value to add to a document. * @method patchBuilderMetadataValues#add * @since 2.0.1 * @param {string} name - the name of the new metadata value * @param value - the value of the new metadata value * @returns {patchBuilder.PatchOperation} a patch operation */ function addMetadataValue(name, value) { if (typeof name !== 'string' || typeof value !== 'string' ) { throw new Error('metadataValues.add() takes a string name and string value'); } var metaVal = {}; metaVal[name] = value; return insert('/object-node("metadataValues")', 'last-child', metaVal); } /** * Specifies a metadata value to replace for a document. * @method patchBuilderMetadataValues#replace * @since 2.0.1 * @param {string} name - the name of the existing metadata value * @param value - the modified value * @returns {patchBuilder.PatchOperation} a patch operation */ function replaceMetadataValue(name, value) { if (typeof name !== 'string' || typeof value !== 'string') { throw new Error('metadataValues.replace() takes a string name and a string value'); } return replace('/metadataValues/node("'+name+'")', value); } /** * Specifies a metadata value to remove from the document. * @method patchBuilderMetadataValues#remove * @since 2.0.1 * @param {string} name - the name of the metadata value to remove * @returns {patchBuilder.PatchOperation} a patch operation */ function removeMetadataValue(name) { if (typeof name !== 'string') { throw new Error('metadataValues.remove() takes a string name'); } return remove('/metadataValues/node("'+name+'")'); } module.exports = { /** * Provides functions that specify patch operations on the collections * to which a document belongs. * @name collections * @since 1.0 * @memberof! patchBuilder# * @type {patchBuilderCollections} */ collections: { add: addCollection, remove: removeCollection }, /** * Provides functions that specify patch operations on the permissions * of a document. * @name permissions * @since 1.0 * @memberof! patchBuilder# * @type {patchBuilderPermissions} */ permissions: { add: addPermission, replace: replacePermission, remove: removePermission }, /** * Provides functions that specify patch operations on the metadata properties * of a document. * @name properties * @since 1.0 * @memberof! patchBuilder# * @type {patchBuilderProperties} */ properties: { add: addProperty, replace: replaceProperty, remove: removeProperty }, /** * Provides functions that specify patch operations on the ranking quality * of a document. * @name quality * @since 1.0 * @memberof! patchBuilder# * @type {patchBuilderQuality} */ quality: { set: setQuality }, /** * Provides functions that specify patch operations on the metadata values * for a document. * @name metadataValues * @since 2.0.1 * @memberof! patchBuilder# * @type {patchBuilderMetadataValues} */ metadataValues: { add: addMetadataValue, replace: replaceMetadataValue, remove: removeMetadataValue }, add: add, apply: apply, concatAfter: concatAfter, concatBefore: concatBefore, concatBetween: concatBetween, datatype: qb.builder.datatype, divideBy: divideBy, insert: insert, library: library, multiplyBy: multiplyBy, pathLanguage: pathLanguage, remove: remove, replace: replace, replaceInsert: replaceInsert, replaceRegex: replaceRegex, substringAfter: substringAfter, substringBefore: substringBefore, subtract: subtract };