UNPKG

faunadb

Version:

FaunaDB Javascript driver for Node.JS and Browsers

312 lines (270 loc) 6.66 kB
'use strict' var util = require('./_util') /** * A representation of a FaunaDB Query Expression. Generally, you shouldn't need * to use this class directly; use the Query helpers defined in {@link module:query}. * * @param {Object} obj The object that represents a Query to be treated as an Expression. * @constructor */ function Expr(obj) { this.raw = obj } Expr.prototype._isFaunaExpr = true Expr.prototype.toJSON = function() { return this.raw } Expr.prototype.toFQL = function() { return exprToString(this.raw) } var varArgsFunctions = [ 'Do', 'Call', 'Union', 'Intersection', 'Difference', 'Equals', 'Add', 'BitAnd', 'BitOr', 'BitXor', 'Divide', 'Max', 'Min', 'Modulo', 'Multiply', 'Subtract', 'LT', 'LTE', 'GT', 'GTE', 'And', 'Or', ] // FQL function names come across the wire as all lowercase letters // (like the key of this object). Not all are properly snake-cased // on the Core side, which causes improper capitalizations. // // JS Driver patch: https://faunadb.atlassian.net/browse/FE-540 // Core update: https://faunadb.atlassian.net/browse/ENG-2110 var specialCases = { containsstrregex: 'ContainsStrRegex', containsstr: 'ContainsStr', endswith: 'EndsWith', findstr: 'FindStr', findstrregex: 'FindStrRegex', gt: 'GT', gte: 'GTE', is_nonempty: 'is_non_empty', lowercase: 'LowerCase', lt: 'LT', lte: 'LTE', ltrim: 'LTrim', ngram: 'NGram', rtrim: 'RTrim', regexescape: 'RegexEscape', replacestr: 'ReplaceStr', replacestrregex: 'ReplaceStrRegex', startswith: 'StartsWith', substring: 'SubString', titlecase: 'TitleCase', uppercase: 'UpperCase', } /** * * @param {Expr} expression A FQL expression * @returns {Boolean} Returns true for valid expressions * @private */ function isExpr(expression) { return ( expression instanceof Expr || util.checkInstanceHasProperty(expression, '_isFaunaExpr') ) } /** * * @param {Object} obj An object to print * @returns {String} String representation of object * @private */ function printObject(obj) { return ( '{' + Object.keys(obj) .map(function(k) { return '"' + k + '"' + ': ' + exprToString(obj[k]) }) .join(', ') + '}' ) } /** * * @param {Array} arr An array to print * @param {Function} toStr Function used for stringification * @returns {String} String representation of array * @private */ function printArray(arr, toStr) { return arr .map(function(item) { return toStr(item) }) .join(', ') } /** * * @param {String} fn A snake-case FQL function name * @returns {String} The correpsonding camel-cased FQL function name * @private */ function convertToCamelCase(fn) { // For FQL functions with special formatting concerns, we // use the specialCases object above to define their casing. if (fn in specialCases) fn = specialCases[fn] return fn .split('_') .map(function(str) { return str.charAt(0).toUpperCase() + str.slice(1) }) .join('') } var exprToString = function(expr, caller) { // If expr is a Expr, we want to parse expr.raw instead if (isExpr(expr)) { if ('value' in expr) return expr.toString() expr = expr.raw } // Return early to avoid extra work if null if (expr === null) { return 'null' } // Return stringified value if expression is not an Object or Array switch (typeof expr) { case 'string': return JSON.stringify(expr) case 'symbol': case 'number': case 'boolean': return expr.toString() case 'undefined': return 'undefined' } // Handle expression Arrays if (Array.isArray(expr)) { var array = printArray(expr, exprToString) return varArgsFunctions.indexOf(caller) != -1 ? array : '[' + array + ']' } // Parse expression Objects if ('match' in expr) { var matchStr = exprToString(expr['match']) var terms = expr['terms'] || [] if (isExpr(terms)) terms = terms.raw if (Array.isArray(terms) && terms.length == 0) return 'Match(' + matchStr + ')' if (Array.isArray(terms)) { return ( 'Match(' + matchStr + ', [' + printArray(terms, exprToString) + '])' ) } return 'Match(' + matchStr + ', ' + exprToString(terms) + ')' } if ('paginate' in expr) { var exprKeys = Object.keys(expr) if (exprKeys.length === 1) { return 'Paginate(' + exprToString(expr['paginate']) + ')' } var expr2 = Object.assign({}, expr) delete expr2['paginate'] return ( 'Paginate(' + exprToString(expr['paginate']) + ', ' + printObject(expr2) + ')' ) } if ('let' in expr && 'in' in expr) { var letExpr = '' if (Array.isArray(expr['let'])) letExpr = '[' + printArray(expr['let'], printObject) + ']' else letExpr = printObject(expr['let']) return 'Let(' + letExpr + ', ' + exprToString(expr['in']) + ')' } if ('object' in expr) return printObject(expr['object']) if ('merge' in expr) { if (expr.lambda) { return ( 'Merge(' + exprToString(expr.merge) + ', ' + exprToString(expr.with) + ', ' + exprToString(expr.lambda) + ')' ) } return ( 'Merge(' + exprToString(expr.merge) + ', ' + exprToString(expr.with) + ')' ) } if ('lambda' in expr) { return ( 'Lambda(' + exprToString(expr['lambda']) + ', ' + exprToString(expr['expr']) + ')' ) } if ('filter' in expr) { return ( 'Filter(' + exprToString(expr['collection']) + ', ' + exprToString(expr['filter']) + ')' ) } if ('call' in expr) { return ( 'Call(' + exprToString(expr['call']) + ', ' + exprToString(expr['arguments']) + ')' ) } if ('map' in expr) { return ( 'Map(' + exprToString(expr['collection']) + ', ' + exprToString(expr['map']) + ')' ) } if ('foreach' in expr) { return ( 'Foreach(' + exprToString(expr['collection']) + ', ' + exprToString(expr['foreach']) + ')' ) } var keys = Object.keys(expr) var fn = keys[0] fn = convertToCamelCase(fn) // The filter prevents zero arity functions from having a null argument // This only works under the assumptions // that there are no functions where a single 'null' argument makes sense. var args = keys .filter(k => expr[k] !== null || keys.length > 1) .map(k => exprToString(expr[k], fn)) .join(', ') return fn + '(' + args + ')' } Expr.toString = exprToString module.exports = Expr