Skip to content

Instantly share code, notes, and snippets.

@jmakeig

jmakeig/apply-as-8.5.sjs

Last active Feb 5, 2020
Embed
What would you like to do?
Evaluate a function in MarkLogic in a different context, such as against a different database, in another transaction, or as another user
/**
* Return a function proxy to invoke a function in another context.
* The proxy can be called just like the original function, with the
* same arguments and return types. Example uses: to run the input
* as another user, against another database, or in a separate
* transaction.
*
* @param {Function} fct The function to invoke
* @param {Object} [options] The `xdmp.eval` options.
* Use `options.user` as a shortcut to
* specify a user name (versus an ID).
* `options.database` can take a `string`
* or a `number`.
* @param {Object} [thisArg] The `this` context when calling `fct`
* @return {Function} A function that accepts the same arguments as
* the originally input function.
*/
function applyAs(fct, options, thisArg) {
return function _wrappedApplyAs() {
var args = Array.prototype.slice.call(arguments);
console.log(`args: ${args}`);
// Curry the function to include the params by closure.
// xdmp.invokeFunction requires that invoked functions have
// an arity of zero.
var f = function _internalArrayWrapper() {
// Nested Sequences are flattened. Thus if `fct` returns a Seqence
// there’s no way to differentiate it from the Sequence that
// `xdmp.invokeFunction` (or `xdmp.eval` or `xdmp.invoke` or `xdmp.spawn`)
// return. However, by wrapping the returned Sequence in something else—
// an array here—we can “pop” the stack to get the acutual return value.
return [fct.apply(thisArg, args)];
};
options = options || {};
// Allow passing in database name, rather than id
if ("string" === typeof options.database) {
options.database = xdmp.database(options.database);
}
// Allow passing in user name, rather than id
if (options.user) {
options.userId = xdmp.user(options.user);
delete options.user;
}
// Allow the functions themselves to declare their transaction mode
if (fct.transactionMode && !options.transactionMode) {
options.transactionMode = fct.transactionMode;
}
return fn.head(xdmp.invokeFunction(f, options)).pop();
};
}
/**
* Return a function proxy to invoke a function in another context.
* The proxy can be called just like the original function, with the
* same arguments and return types. Example uses: to run the input
* as another user, against another database, or in a separate
* transaction.
*
* @param {Function} fct The function to invoke
* @param {Object} [options] The `xdmp.eval` options.
* Use `options.user` as a shortcut to
* specify a user name (versus an ID).
* `options.database` can take a `string`
* or a `number`.
* @param {Object} [thisArg] The `this` context when calling `fct`
* @return {Function} A function that accepts the same arguments as
* the originally input function.
*/
function applyAs(fct, options, thisArg) {
return function _wrappedApplyAs() {
// Curry the function to include the params by closure.
// xdmp.invokeFunction requires that invoked functions have
// an arity of zero.
const f = () => {
// Nested Sequences are flattened. Thus if `fct` returns a Seqence
// there’s no way to differentiate it from the Sequence that
// `xdmp.invokeFunction` (or `xdmp.eval` or `xdmp.invoke` or `xdmp.spawn`)
// return. However, by wrapping the returned Sequence in something else—
// an array here—we can “pop” the stack to get the acutual return value.
return [fct.apply(thisArg, arguments)];
};
options = options || {};
// Allow passing in database name, rather than id
if('string' === typeof options.database) { options.database = xdmp.database(options.database); }
// Allow passing in user name, rather than id
if(options.user) { options.userId = xdmp.user(options.user); delete options.user; }
// Allow the functions themselves to declare their transaction mode
if(fct.transactionMode && !(options.transactionMode)) { options.transactionMode = fct.transactionMode; }
return fn.head(xdmp.invokeFunction(f, options)).pop();
//return xdmp.invokeFunction(f, options).toArray().pop().pop(); // <https://bugtrack.marklogic.com/bug/38646>
}
}
// For exmaple, a better MarkLogic eval:
const evil = applyAs(
eval, // Built-in eval
{
database: 'Documents',
isolation: 'different-transaction'
}
);
var out = [xdmp.transaction()];
var transactionSame = applyAs(xdmp.transaction, {isolation: 'same-statement'}, 'one');
var transactionDiff = applyAs(xdmp.transaction, {isolation: 'different-transaction'}, 'one');
out.concat([transactionSame(), transactionDiff()]);
/*
[
"9830740160355080717",
"9830740160355080717", // same
"15639343846052397268" // different
]
*/
/**
* Creates a new function that maps over the iterable results of another function.
* If the return of the input function isn’t iterable, it just yields the result.
* Strings aren’t considered Iterable. (And really shouldn’t be…ever.)
* (HOF FTW!)
*
* @example
* const getRoleIDs = map(applyAs(sec.getRoleIds, { database: xdmp.securityDatabase() }), fn.data);
*
* @param {Function} fun - The function whose output will be mapped
* @param {Function} [mapper] - The optional map function. Defaults to the identity function.
* @returns {GeneratorFunction} - A generator over the mapped results of calling `fun`
*/
function map(fct, mapper) {
const ident = item => item;
mapper = mapper || ident;
return function* _map() {
const itr = fct.apply(null, arguments);
if('string' === typeof itr || !(itr[Symbol.iterator])) {
yield itr;
} else {
for(const item of itr) {
yield mapper(item);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.