diff --git a/README.md b/README.md index 8751bf6..8ba64f6 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ JavaScript Object Query Language Representation - Funny it's mostly JSON. JOQULAR is a query language specification. Although there is a reference implementation with this site others are free to write their own drivers. -Like SQL, JOQULAR allows you to mutate the returned records to meet you needs; however, if you do not perform any mutations you will get the matched object back rather than a copy. +Like SQL, JOQULAR allows you to mutate the returned records to meet you needs. Mutation options include property aliasing and redaction, value substitution (including array summaries, sorting, filtering, etc.), adding event handlers, freezing objects and properties. @@ -18,16 +18,19 @@ npm install joqular # Usage -``` -const = await JOQULAR.match(,); +```javascript +const [,...] = await JOQULAR.query(,,...); ``` -``` +```javascript const pattern = {name:{$eq:"joe"}}, [matched1] = await JOQULAR.match(pattern,{name:"joe",age:27}), // will resolve to the object with name "joe" [matched2] = await JOQULAR.match(pattern,{name:"jane",age:27}); // will resolve to undefined ``` +JOQULAR queries can cause mutations. If an object is mutated during a query, a copy is returned. If no mutations occur, the original is returned. If you always want a copy, then do something to force a slight mutation, e.g. add a hidden property with +`$define`. + For now see the [Medium article](https://medium.com/@anywhichway/joqular-high-powered-javascript-pattern-matching-273a0d77eab5) for more information. # API @@ -36,167 +39,301 @@ For now see the [Medium article](https://medium.com/@anywhichway/joqular-high-po All 92 predicates and functions or their aliases could actually be defined inline; however, functiona that are difficult to implement or used frequently are defined as part of JOQULAR. -Below is an alphabetical list with minimal documentation. Future documentation will have far more detail and group functions by type. In the meantime, see the the `index.js` in the test directory and review the unit tests. +New functions can be added in as little as one line of code, e.g. + +```javascript +JOQULAR.function((value,key,object) => ...,"$myfunction"); +``` + +or + +```javascript +JOQULAR.function(function $myfunction(value,key) { ...} ); +``` + +In the second form you can use a third `object` argument if you wish, but the `this` context is also set to the object being tested. In the first form the `this` context can't be used effectively because it is the context from the point at which the function was defined. + +Below is an alphabetical list with minimal documentation. Future documentation will have far more detail and group functions by type. In the meantime, see the `index.js` in the test directory and review the unit tests. + +$ - `{:{$: (value,property,object) => ...}` + +* Calls a function with the property value, name, and object. The function can change the object. Matching continues if the function returns `true`. + +$and - `{$and: }` + +$as - `{:{$as: string as}}` + +* Adds a property `as` on the target with the value of `` on the target. + +$avg - `{: {$avg: null||string as}}` + +* Assumes the current value of the `` on the target is an iterable. Average's the numbers and either replaces the property value with the average if `as` equals `null`. Or, it adds a property with the name `as` and sets it to the average. + +$avga - `{: {$avg: null||string as}}` + +* Same as `$avg`, except `true` and `false` are treated like 1 and 0 respectively and an attempt is made to parse string values as numbers. + +$between - `{: {$between: [number hi,number lo,boolean inclusive]}}` + +* Returns `true` in the value of `` on the target is between `hi` and `lo`, optionally inclusive of the boundaries. + +$compute - `{: {$compute (value,key,object) => ... || Array [function f,string as]}}` + +* Computes a value for `: {$count: number count}}` + +* Returns true if the value of `` equals `count` based on a full evaluation of the underlying iterable on the target ignoring `undefined`. + +$counta - `{: {$counta: number count}}` + +* Similar to `$count` except that is trusts the `.length` or method calls for `count()` on the underlying iterable if available. + +$date - `{: {$date: number dayOfMonth}}` + +* Returns true if the target `` has a Date value where `getDate() === dayOfMonth`. + +$day - `{: {$day: number dayOfWeek}}` + +* Returns true if the target `` has a Date value where `getDay() === dayOfWeek`. + +$default - `{: {$default: any value}}` + +* Sets value of target `` to `value` if it is undefined. + +$define - `{: {$define: {enumerable,configurable,writable[,value]}}` + +* Defines the `` on the target object using the descriptor provided and sets the value to that of the value already on the target. If the optional `value` is provided on the descriptor, uses that instead of the current value on the target and creates the property if it does not exist. + +$descendant - `{: {$descendant: }}` -$ +* Returns true if any descendant of the property on the target matches the ``. -$and +$disjoint - `{: {$disjoint: Array array}}` -$as +* Returns true if the array on the target `` is disjoint (has no values in common) with `array`. -$avg +$echoes - `{: {$echoes: string value}}` -$avga +* Returns the result of `soundex()===soundex(value)`. -$between +$eeq - `{: {$eeq: string||number||boolean||object value}}` -$compute +* Returns the result of ` === value`. -$count +$eq - `{: {$eq: string||number||boolean||object value}}` -$counta +* Returns the result of ` == value` or in the case of `object`, is deep equal to. -$date +$every - -$day +$excludes - -$default +$extract - `{: {$extract: }}` -$define +Replaces the value of the `` on the target with only the portions of its children explicity referenced in ``. -$descendant +$false - -$disjoint +$filter - -$echoes +$forDescendant - `{: {$forDescendant: {pattern,f,depth=Infinity}}` -$eeq +Walks the descendant tree of the `: {$fullYear: number fullYear}}` -$excludes +* Returns true if the target `` has a Date value where `getFullYear() === fullYear`. -$extract +$gt - `{: {$gte: string||number||boolean value}}` -$false +* Returns the result of ` > value`. -$filter +$gte - `{: {$gte: string||number||boolean value}}` -$forDescendant +* Returns the result of ` >= value`. -$freeze +$hours - `{: {$hours: number hours}}` -$fullYear +* Returns true if the target `` has a Date value where `getHours() === hours`. -$gt +$in - -$gte +$includes - -$hours +$instanceof - -$in +$intersection - `{: {$intersection: Array array}}` -$includes +* Sets the `` on the target to the intersection of the array on the target with `array`. If the property does not contain an array, the result is an emtpy array since there can be no intersection. If `array` has an `as` and additional property is added to hold the intersection. -$instanceof +$intersects - `{: {$intersects: Array array}}` -$intersects +* Returns true if the array on the target `` intersects with `array`. -$isAny +$isAny - `{: {$isAny: types=["boolean","function","number","object","string"]}}` -$isArray +* Returns the result of `types.includes()`. By default this is all types except "undefined". -$isCreditCard +$isArray - `{: {$isArray: null}}` -$isEmail +* Returns the result of `Array.isArray()`. -$isEven +$isCreditCard - `{: {$isCreditCard: string||number||boolean value}}` -$isFrozen +* Returns true if the value of the target `` satifies the Luhn algorithm. -$isIPAddress +$isEmail - -$isNaN +$isEven - `{: {$isEven: null}}` -$isOdd +* Returns the result of ` % 2 === 0`. -$isSSN +$isFrozen - `{: {$isFrozen: null}}` -$length +* Returns the result of `Object.isFrozen()`. -$lt +$isIPAddress `{: {$isIPAddress: null}}` -$lte +* Returns `true` is the target `` satifies the regular expression: -$map +* (/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/m). -$matches +$isNaN - `{: {$isNan: null}}` -$max +* Returns true if the value of the target `` `satisfies isNaN(value)`. -$maxa +$isOdd - `{: {$isOdd: null}}` -$milliseconds +* Returns the result of ` % 2 !== 0` -$min +$isSSN - `{: {$isSSN: null}}` -$mina +* Returns `true` if the target `` value satisfies the regular expression: -$minutes +* /^\d{3}-?\d{2}-?\d{4}$/. -$month +$length - `{: {$length: value}}` -$ne +* Returns the result of `.length === value`. -$neeq +$lt - `{: {$lt: string||number||boolean value}}` -$neq +* Returns the result of ` < value`. + +$lte - `{: {$lte: string||number||boolean value}}` + +* Returns the result of ` <= value`. + +$map - + +$matches - + +$max - `{: {$max: string||number||boolean value}}` + +* Assumes the current value of the `` on the target is an iterable. Get's the max and either replaces the property value with the max if `as` equals `null`. Or, it adds a property with the name `as` and sets it to the max. + +$maxa - `{: {$max: string||number||boolean value}}` + +* Same as `$max`, except `true` and `false` are treated like 1 and 0 respectively and an attempt is made to parse string values as numbers. + +$milliseconds - `{: {$milliseconds: number milliseconds}}` + +* Returns true if the target `` has a Date value where `getMilliseconds() === milliseconds`. + +$min - `{: {$min: string||number||boolean value}}` + +* Assumes the current value of the `` on the target is an iterable. Get's the min and either replaces the property value with the min if `as` equals `null`. Or, it adds a property with the name `as` and sets it to the min. + +$mina - `{: {$min: string||number||boolean value}}` + +* Same as `$min`, except `true` and `false` are treated like 1 and 0 respectively and an attempt is made to parse string values as numbers. + +$minutes - `{: {$minutes: number minutes}}` + +* Returns true if the target `` has a Date value where `getMinutes() === minutes`. + +$month - `{: {$month: number month}}` + +* Returns true if the target `` has a Date value where `getMonth() === month`. + +$ne - Alias for `$neq`. + +$neeq - `{: {$neeq: string||number||boolean||object value}}` + +* Returns true if the value of the target `` `!== value`. + +$neq - `{: {$neq: string||number||boolean||object value}}` + +* Returns true if the value of the target `` `!= value` or in the case of an object are not deep equal. $nin $not -$on +$on - `{:{$on:{get,set,delete,onError}}}` + +Adds handlers to the target `` that are invoked for `get`, `set`, and `delete`. The handlers take the standard form `(object,key,value[,oldvalue]) => ...` with only `set` getting `oldvalue`. If a handler returns a `Promise` it is NOT awaited. + +If `onError` is provided it should have the signature `(error,object,key,value[,oldvalue]) => ...`. The `onError` function can chose to swallow the error or re-throw it. Note, if the error is critical and the desrie is to abort the attempted action, the `onError` handler must be synchronus, i.e. not return a `Promise` or throw from a timeout. + +$or - + +$outside - -$or +$readonly - `{: {$readonly: null}}` -$outside +Returns true if the `` exists on the target and is read-only. -$readonly +$redact - `{: {$redact null||(key,value,object) => ...}}` -$redact +Deletes the `` from the target if argument is `null` or the provided function returns true. -$reduce +$reduce - -$regexp +$regexp - -$return +$return - `{: {$return: any value}}` -$sample +Sets the value on the target `` to `value`. -$search +$sample - `{: {$sample: number 0.percentage||[pct,max=Infinity,as]}}` -$seconds +Assumes the target `` is an `Iterable` and computes a random sample of `max` size or `percentage * size`. The sample is stored in the target property unless `as` is specified to add a property. The `percentage` is a decimal between zero and one. The chance of any given item being picked is independenty equal to the `percentage`. This is because for some iterables the size is not known until the entire iterable has been processed. Be careful not to try and sample an infinitely yielding `Iterator` without providing a length. -$some +$search - -$sort +$seconds - `{: {$seconds: number seconds}}` -$sum +* Returns true if the target `` has a Date value where `getSeconds() === seconds`. -$suma +$some - -$text +$sort - -$time +$sum - `{: {$sum: null||string as}}` -$true +* Assumes the current value of the `` on the target is an iterable. Average's the numbers and either replaces the property value with the average if `as` equals `null`. Or, it adds a property with the name `as` and sets it to the average. -$type +$suma - `{: {$sum: null||string as}}` -$typeof +* Same as `$sum`, except `true` and `false` are treated like 1 and 0 respectively and an attempt is made to parse string values as numbers. + +$text - + +$time - `{: {$time: number time}}` + +* Returns true if the target `` has a Date value where `getTime() === time`. + +$true - + +$type - Alias for `$typeof`. + +$typeof - `{: {$typeof: string type}}` + +* Returns true if the value of the target `` `== type`. $UTCDate @@ -222,12 +359,17 @@ $where $xor -$year +$year - `{: {$year: number year}}` + +* Returns true if the target `` has a Date value where `getYear() === year`. ## Updates -2019-02-10 v2.0.1b - `.match` now returns an array and can take multiple objects to match against. Added event handlers via $on. Now returns source object if no-mutations have occured. +2019-02-11 v2.02b - Lots of documentation. Started deprecation on method name `.match` in favor of `.query`, but both will work for now through aliasing. + +2019-02-10 v2.0.1b - `.match` now returns an array and can take multiple objects to match against. Added event handlers via +`$on`. Now returns source object if no-mutations have occured. 2019-02-10 v2.0.0b - Initial public reference implementation subsequent to deprecation of JOQULAR as an in memory datastore. diff --git a/index.js b/index.js index 7753b86..fdbe99f 100644 --- a/index.js +++ b/index.js @@ -156,20 +156,20 @@ }, extract = async (pattern,value) => { const extracted = {}; - if(matchaux(pattern,value,extracted)) { + if(queryaux(pattern,value,extracted)) { return extracted; } }, - match = async (pattern,...values) => { + query = async (pattern,...values) => { const results = []; for(const value of values) { const copy = deepCopy(value), - matched = await matchaux(pattern,copy,{}); - results.push(deepEqual(matched,value) ? value : matched); + queryed = await queryaux(pattern,copy,{}); + results.push(deepEqual(queryed,value) ? value : queryed); } return results; }, - matchaux = async (pattern,value,extracted,objectKey,object) => { + queryaux = async (pattern,value,extracted,objectKey,object) => { if(pattern===null || pattern===value) return value; const ptype=typeof(pattern), vtype = typeof(value); @@ -202,12 +202,12 @@ predicate = FUNCTIONS[key] ? FUNCTIONS[key] : typeof(pattern[key])==="function" ? pattern[key] : undefined; if(pkeytype!=="function" && predicate) { if(typeof(pattern[key])==="function" && !FUNCTIONS[key]) { - if(!(await predicate.call(value,value[key],predicate,key))) { + if(!(await predicate.call(value,value[key],predicate,key,value))) { return; } extracted[key] = value[key]; } else { - if(!(await predicate.call(object,value,pattern[key],objectKey))) { + if(!(await predicate.call(object,value,pattern[key],objectKey,object))) { return; } if(value===undefined) { @@ -224,11 +224,11 @@ return; } for(const vkey of keys) { - if((await matchaux(pattern[key],value[vkey],extracted,vkey,value)===undefined)) { + if((await queryaux(pattern[key],value[vkey],extracted,vkey,value)===undefined)) { return; } } - } else if((await matchaux(pattern[key],value[key],extracted,key,value)===undefined)) { + } else if((await queryaux(pattern[key],value[key],extracted,key,value)===undefined)) { return; } } @@ -262,7 +262,6 @@ } }, $as(value,as,key) { - delete this[key]; this[as] = value; return true; }, @@ -359,7 +358,7 @@ return true; }, async $descendant(target,pattern,key) { - if(JOQULAR.match(pattern,target,{},key,this)!==undefined) return true; + if(JOQULAR.query(pattern,target,{},key,this)!==undefined) return true; if(!target || typeof(target)!=="object") return true; for(const key of Object.keys(target)) { if((await FUNCTIONS.$descendant.call(target,target[key],pattern,key))) { @@ -376,7 +375,10 @@ $eeq(a,b) { return a === b; }, - $eq(a,b) { + $eq(a,b) { + if(a && b && typeof(a)==="object" && typeof(b)==="object") { + return deepEqual(a,b); + } return a == b; }, async $every(iterable,f) { @@ -399,7 +401,7 @@ }, async $extract(target,pattern,key) { const extracted = {}; - if((await JOQULAR.match(pattern,target,extracted,key,this))!==undefined) { + if((await JOQULAR.query(pattern,target,extracted,key,this))!==undefined) { this[key] = extracted; } return true; @@ -432,11 +434,14 @@ this[as||key] = result; return true; }, - async $forDescendant(target,{pattern,$,depth=Infinity},key) { - JOQULAR.match(pattern,target,{},key,this); - if(!target || typeof(target)!=="object" || depth===0) return true; + async $forDescendant(target,{pattern,f,depth=Infinity},key,seen=new Set()) { + if(JOQULAR.query(pattern,target,{},key,this)!==undefined) { + await f(target,key,this); + } + if(!target || typeof(target)!=="object" || depth===0 && !seen.has(target)) return true; + seen.add(target); for(const key of Object.keys(target)) { - await FUNCTIONS.$forDescendant.call(target,target[key],{pattern,$,depth:depth-1},key); + await $forDescendant.call(target,target[key],{pattern,$,depth:depth-1},key,seen); } return true; }, @@ -488,8 +493,8 @@ $intersects(a,b) { return Array.isArray(a) && Array.isArray(b) && intersection(a,b).length>0; }, - $isAny() { - return true; + $isAny(a,types=["boolean","function","number","object","string"]) { + return types.includes(typeof(a)); }, async $isArray(value) { return Array.isArray(value); @@ -551,8 +556,8 @@ }, $matches(value,regexp,key) { if(value) { - const match = value.matches||value.match; - if(typeof(match)==="function") { + const query = value.matches||value.query; + if(typeof(query)==="function") { let as; if(Array.isArray(regexp)) { as = regexp[2]; @@ -562,7 +567,7 @@ ; } } - const matches = match(regexp); + const matches = query(regexp); if(matches) { this[as||key] = matches; return true; @@ -641,6 +646,9 @@ } }, $neq(a,b) { + if(a && b && typeof(a)==="object" && typeof(b)==="object") { + return !deepEqual(a,b); + } return a != b; }, $neeq(a,b) { @@ -700,7 +708,7 @@ f(proxy,key,value); } catch(e) { if(f.onError) { - f.onError(e); + f.onError(e,proxy,key,value); } else { throw(e); } @@ -717,9 +725,8 @@ try { f(proxy,key,value,oldvalue); } catch(e) { - error = e; if(f.onError) { - error = f.onError(e); + f.onError(e,proxy,key,value,oldvalue); } else { throw(e); } @@ -739,9 +746,8 @@ try { f(proxy,key,value); } catch(e) { - error = e; if(f.onError) { - error = f.onError(e); + f.onError(e,proxy,key,value); } else { throw(e); } @@ -781,15 +787,23 @@ } }, $readonly(value,_,key) { - try { - this[key] = value; - return false; - } catch(e) { - return true; + if(this[key]!==undefined || Object.getOwnPropertyDescriptor(this,key)) { + try { + this[key] = value; // setting to is existing value, so no harm + return false; + } catch(e) { + return true; + } } }, - $redact(_1,_2,key) { - delete this[key]; + async $redact(value,f,key) { + if(typeof(f)==="function") { + if(await f(value,key,this)) { + delete this[key]; + } + } else { + delete this[key]; + } return true; }, async $reduce(iterable,spec,key) { @@ -824,6 +838,7 @@ // get count random items from an iterable if(Symbol.iterator in Object(iterable)) { for(const item of iterable) { + i++; const rand = Math.random(); if(rand<=pct) { if(Array.isArray(iterable)) { @@ -835,7 +850,8 @@ } } } - this[as||key] = sample; + const length = Math.min(max,Math.round(i * pct)); + this[as||key] = sample.slice(0,length); return true; }, async $search(text,phrase) { @@ -992,7 +1008,8 @@ Object.defineProperty(JOQULAR,"undefine",{value:deleteFunction}); Object.defineProperty(JOQULAR,"register",{value:register}); Object.defineProperty(JOQULAR,"unregister",{value:unregister}); - Object.defineProperty(JOQULAR,"match",{value:match}); + Object.defineProperty(JOQULAR,"query",{value:query}); + Object.defineProperty(JOQULAR,"match",{value:query}); Object.assign(FUNCTIONS,functions); if(typeof(module)!=="undefined") module.exports = JOQULAR; diff --git a/package.json b/package.json index b09da48..bad47c3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "joqular", - "version": "2.0.1b", + "version": "2.0.2b", "description": "JOQULAR (JavaScript Object Query Language Representation) for JSON data matching, transformation, validation and extraction", "main": "index.js", "scripts": { diff --git a/test/index.js b/test/index.js index e0a6677..8f65ead 100644 --- a/test/index.js +++ b/test/index.js @@ -47,331 +47,331 @@ notnow.setUTCFullYear(yr+1); describe("Test",function() { it("double property",function() { - return JOQULAR.match({name:"joe",age:27},testobject) + return JOQULAR.query({name:"joe",age:27},testobject) .then(([object]) => testvalidator(object)) }); it("inline property",function() { - return JOQULAR.match({[key => key==="age"]:{$lt:28}},testobject) + return JOQULAR.query({[key => key==="age"]:{$lt:28}},testobject) .then(([object]) => testvalidator(object)) }); it("inline value true",function() { - return JOQULAR.match({age:value => value < 28},testobject) + return JOQULAR.query({age:value => value < 28},testobject) .then(([object]) => testvalidator(object)) }); it("inline value false",function() { - return JOQULAR.match({age:value => value < 27},testobject) + return JOQULAR.query({age:value => value < 27},testobject) .then(([object]) => testvalidator(object)).catch(() => true); }); it("functional key",function() { - return JOQULAR.match({[key => key==="name"]:{$typeof:"string"}},testobject) + return JOQULAR.query({[key => key==="name"]:{$typeof:"string"}},testobject) .then(([object]) => testvalidator(object)) }); it("RegExp key",function() { - return JOQULAR.match({[/.*name/]:{$eq: "joe"}},{name:"joe",age:27,race:"caucasian"}) + return JOQULAR.query({[/.*name/]:{$eq: "joe"}},{name:"joe",age:27,race:"caucasian"}) .then(([object]) => testvalidator(object)) }); xit("$",function() { - return JOQULAR.match({name:{$:value=>value==="joe"}},testobject).then(([object]) => { testvalidator(object); }) + return JOQULAR.query({name:{$:value=>value==="joe"}},testobject).then(([object]) => { testvalidator(object); }) }); it("$lt",function() { - return JOQULAR.match({age:{$lt:28}},testobject).then(([object]) => testvalidator(object)) + return JOQULAR.query({age:{$lt:28}},testobject).then(([object]) => testvalidator(object)) }); it("$lt fail",function(done) { - JOQULAR.match({age:{$lt:27}},testobject).then(([object]) => testvalidator(object)).catch(()=>done()) + JOQULAR.query({age:{$lt:27}},testobject).then(([object]) => testvalidator(object)).catch(()=>done()) }); it("$lte",function() { - return JOQULAR.match({age:{$lte:27}},testobject).then(([object]) => testvalidator(object)) + return JOQULAR.query({age:{$lte:27}},testobject).then(([object]) => testvalidator(object)) }); it("$lte fail",function(done) { - JOQULAR.match({age:{$lte:26}},testobject).then(([object]) => testvalidator(object)).catch(()=>done()) + JOQULAR.query({age:{$lte:26}},testobject).then(([object]) => testvalidator(object)).catch(()=>done()) }); it("$eq",function() { - return JOQULAR.match({age:{$eq:27}},testobject).then(([object]) => testvalidator(object)) + return JOQULAR.query({age:{$eq:27}},testobject).then(([object]) => testvalidator(object)) }); it("$eq fail",function(done) { - JOQULAR.match({age:{$eq:26}},testobject).then(([object]) => testvalidator(object)).catch(()=>done()) + JOQULAR.query({age:{$eq:26}},testobject).then(([object]) => testvalidator(object)).catch(()=>done()) }); it("$eq string",function() { - return JOQULAR.match({age:{$eq:"27"}},testobject).then(([object]) => testvalidator(object)) + return JOQULAR.query({age:{$eq:"27"}},testobject).then(([object]) => testvalidator(object)) }); it("$eq string fail",function(done) { - JOQULAR.match({age:{$eq:"26"}},testobject).then(([object]) => testvalidator(object)).catch(()=>done()) + JOQULAR.query({age:{$eq:"26"}},testobject).then(([object]) => testvalidator(object)).catch(()=>done()) }); it("$eeq",function() { - return JOQULAR.match({age:{$eeq:27}},testobject).then(([object]) => testvalidator(object)) + return JOQULAR.query({age:{$eeq:27}},testobject).then(([object]) => testvalidator(object)) }); it("$eeq fail",function(done) { - JOQULAR.match({age:{$eeq:28}},testobject).then(([object]) => testvalidator(object)).catch(()=>done()) + JOQULAR.query({age:{$eeq:28}},testobject).then(([object]) => testvalidator(object)).catch(()=>done()) }); it("$neq string",function() { - return JOQULAR.match({age:{$neq:"5"}},testobject).then(([object]) => testvalidator(object)) + return JOQULAR.query({age:{$neq:"5"}},testobject).then(([object]) => testvalidator(object)) }); it("$neq string fail",function(done) { - JOQULAR.match({age:{$neq:"27"}},testobject).then(([object]) => testvalidator(object)).catch(()=>done()) + JOQULAR.query({age:{$neq:"27"}},testobject).then(([object]) => testvalidator(object)).catch(()=>done()) }); it("$neeq",function() { - return JOQULAR.match({age:{$neeq:5}},testobject).then(([object]) => testvalidator(object)) + return JOQULAR.query({age:{$neeq:5}},testobject).then(([object]) => testvalidator(object)) }); it("$neeq fail",function(done) { - JOQULAR.match({age:{$neeq:27}},testobject).then(([object]) => testvalidator(object)).catch(() =>done()) + JOQULAR.query({age:{$neeq:27}},testobject).then(([object]) => testvalidator(object)).catch(() =>done()) }); it("$between",function() { - return JOQULAR.match({age:{$between:[26,28]}},testobject).then(([object]) => testvalidator(object)) + return JOQULAR.query({age:{$between:[26,28]}},testobject).then(([object]) => testvalidator(object)) }); it("$between fail",function(done) { - JOQULAR.match({age:{$between:[30,31]}},testobject).then(([object]) => testvalidator(object)).catch(() =>done()) + JOQULAR.query({age:{$between:[30,31]}},testobject).then(([object]) => testvalidator(object)).catch(() =>done()) }); it("$between inclusive",function() { - return JOQULAR.match({age:{$between:[27,28,true]}},testobject).then(([object]) => testvalidator(object)) + return JOQULAR.query({age:{$between:[27,28,true]}},testobject).then(([object]) => testvalidator(object)) }); it("$between inclusive fail",function(done) { - JOQULAR.match({age:{$between:[30,318,true]}},testobject).then(([object]) => testvalidator(object)).catch(() =>done()) + JOQULAR.query({age:{$between:[30,318,true]}},testobject).then(([object]) => testvalidator(object)).catch(() =>done()) }); it("$outside higher",function() { - return JOQULAR.match({age:{$outside:[25,26]}},testobject).then(([object]) => testvalidator(object)) + return JOQULAR.query({age:{$outside:[25,26]}},testobject).then(([object]) => testvalidator(object)) }); it("$outside higher fail",function(done) { - JOQULAR.match({age:{$outside:[25,28]}},testobject).then(([object]) => testvalidator(object)).catch(() =>done()) + JOQULAR.query({age:{$outside:[25,28]}},testobject).then(([object]) => testvalidator(object)).catch(() =>done()) }); it("$outside lower",function() { - return JOQULAR.match({age:{$outside:[28,29]}},testobject).then(([object]) => testvalidator(object)) + return JOQULAR.query({age:{$outside:[28,29]}},testobject).then(([object]) => testvalidator(object)) }); it("$outside lower fail",function(done) { - JOQULAR.match({age:{$outside:[26,29]}},testobject).then(([object]) => testvalidator(object)).catch(() =>done()) + JOQULAR.query({age:{$outside:[26,29]}},testobject).then(([object]) => testvalidator(object)).catch(() =>done()) }); it("$gte",function() { - return JOQULAR.match({age:{$gte:27}},testobject).then(([object]) => testvalidator(object)) + return JOQULAR.query({age:{$gte:27}},testobject).then(([object]) => testvalidator(object)) }); it("$gte fail",function(done) { - JOQULAR.match({age:{$gte:28}},testobject).then(([object]) => testvalidator(object)).catch(() =>done()) + JOQULAR.query({age:{$gte:28}},testobject).then(([object]) => testvalidator(object)).catch(() =>done()) }); it("$gt",function() { - return JOQULAR.match({age:{$gt:26}},testobject).then(([object]) => testvalidator(object)) + return JOQULAR.query({age:{$gt:26}},testobject).then(([object]) => testvalidator(object)) }); it("$gt fail",function(done) { - JOQULAR.match({age:{$gt:28}},testobject).then(([object]) => testvalidator(object)).catch(() =>done()) + JOQULAR.query({age:{$gt:28}},testobject).then(([object]) => testvalidator(object)).catch(() =>done()) }); it("$instanceof by string",function() { - return JOQULAR.match({favoriteNumbers:{$instanceof:"Array"}},testobject).then(([object]) => testvalidator(object)) + return JOQULAR.query({favoriteNumbers:{$instanceof:"Array"}},testobject).then(([object]) => testvalidator(object)) }); it("$instanceof by string fail",function(done) { - JOQULAR.match({favoriteNumbers:{$instanceof:"Function"}},testobject).then(([object]) => testvalidator(object)).catch(() =>done()) + JOQULAR.query({favoriteNumbers:{$instanceof:"Function"}},testobject).then(([object]) => testvalidator(object)).catch(() =>done()) }); it("$instanceof by constructor",function() { - return JOQULAR.match({favoriteNumbers:{$instanceof:Array}},testobject).then(([object]) => testvalidator(object)) + return JOQULAR.query({favoriteNumbers:{$instanceof:Array}},testobject).then(([object]) => testvalidator(object)) }); it("$instanceof by constructor fail",function(done) { - JOQULAR.match({favoriteNumbers:{$instanceof:Function}},testobject).then(([object]) => testvalidator(object)).catch(() =>done()) + JOQULAR.query({favoriteNumbers:{$instanceof:Function}},testobject).then(([object]) => testvalidator(object)).catch(() =>done()) }); it("$isArray",function() { - return JOQULAR.match({favoriteNumbers:{$isArray:null}},testobject).then(([object]) => testvalidator(object)) + return JOQULAR.query({favoriteNumbers:{$isArray:null}},testobject).then(([object]) => testvalidator(object)) }); it("$isArray fail",function(done) { - JOQULAR.match({age:{$isArray:null}},testobject).then(([object]) => testvalidator(object)).catch(() =>done()) + JOQULAR.query({age:{$isArray:null}},testobject).then(([object]) => testvalidator(object)).catch(() =>done()) }); it("$isEmail",function() { - return JOQULAR.match({email:{$isEmail:null}},testobject).then(([object]) => testvalidator(object)) + return JOQULAR.query({email:{$isEmail:null}},testobject).then(([object]) => testvalidator(object)) }); it("$isEmail fail",function(done) { - JOQULAR.match({name:{$isEmail:null}},testobject).then(([object]) => testvalidator(object)).catch(() =>done()) + JOQULAR.query({name:{$isEmail:null}},testobject).then(([object]) => testvalidator(object)).catch(() =>done()) }); it("$isEven",function() { - return JOQULAR.match({size:{$isEven:null}},testobject).then(([object]) => testvalidator(object)) + return JOQULAR.query({size:{$isEven:null}},testobject).then(([object]) => testvalidator(object)) }); it("$isEven fail",function(done) { - JOQULAR.match({age:{$isEven:null}},testobject).then(([object]) => testvalidator(object)).catch(() =>done()) + JOQULAR.query({age:{$isEven:null}},testobject).then(([object]) => testvalidator(object)).catch(() =>done()) }); it("$isIPAddress",function() { - return JOQULAR.match({ip:{$isIPAddress:null}},testobject).then(([object]) => testvalidator(object)) + return JOQULAR.query({ip:{$isIPAddress:null}},testobject).then(([object]) => testvalidator(object)) }); it("$isIPAddress fail",function(done) { - JOQULAR.match({age:{$isIPAddress:null}},testobject).then(([object]) => testvalidator(object)).catch(() =>done()) + JOQULAR.query({age:{$isIPAddress:null}},testobject).then(([object]) => testvalidator(object)).catch(() =>done()) }); it("$isOdd",function() { - return JOQULAR.match({age:{$isOdd:null}},testobject).then(([object]) => testvalidator(object)) + return JOQULAR.query({age:{$isOdd:null}},testobject).then(([object]) => testvalidator(object)) }); it("$isOdd fail",function(done) { - JOQULAR.match({size:{$isOdd:null}},testobject).then(([object]) => testvalidator(object)).catch(() =>done()) + JOQULAR.query({size:{$isOdd:null}},testobject).then(([object]) => testvalidator(object)).catch(() =>done()) }); it("$isSSN",function() { - return JOQULAR.match({ssn:{$isSSN:null}},testobject).then(([object]) => testvalidator(object)) + return JOQULAR.query({ssn:{$isSSN:null}},testobject).then(([object]) => testvalidator(object)) }); it("$isSSN fail",function(done) { - JOQULAR.match({name:{$isSSN:null}},testobject).then(([object]) => testvalidator(object)).catch(() =>done()) + JOQULAR.query({name:{$isSSN:null}},testobject).then(([object]) => testvalidator(object)).catch(() =>done()) }); for(const key of ["date","day","fullYear","hours","milliseconds","minutes","month","seconds","time","UTCDate","UTCDay","UTCFullYear","UTCHours","UTCSeconds","UTCMilliseconds","UTCMinutes","UTCMonth","year"]) { it(`\$${key}`,function() { - return JOQULAR.match({now:{["$"+key]:now}},testobject).then(([object]) => testvalidator(object)) + return JOQULAR.query({now:{["$"+key]:now}},testobject).then(([object]) => testvalidator(object)) }); if(key.startsWith("UTC")) { it(`\$${key} fail`,function(done) { - JOQULAR.match({now:{["$"+key]:notnow}},testobject).then(([object]) => testvalidator(object)).catch(() =>done()) + JOQULAR.query({now:{["$"+key]:notnow}},testobject).then(([object]) => testvalidator(object)).catch(() =>done()) }); } } it("$min",function() { - return JOQULAR.match({favoriteNumbers:{$min:"minFavorite"}},testobject) + return JOQULAR.query({favoriteNumbers:{$min:"minFavorite"}},testobject) .then(([object]) => { testvalidator(object); chai.expect(object.minFavorite).equal(1); }) }); it("$min fail",function(done) { - JOQULAR.match({favoriteNumbers:{$min:"minFavorite"}},testobject) + JOQULAR.query({favoriteNumbers:{$min:"minFavorite"}},testobject) .then(([object]) => { testvalidator(object); chai.expect(object.minFavorite).equal(2); }).catch(() =>done()) }); it("$avg",function() { - return JOQULAR.match({favoriteNumbers:{$avg:"avgFavorite"}},testobject) + return JOQULAR.query({favoriteNumbers:{$avg:"avgFavorite"}},testobject) .then(([object]) => { testvalidator(object); chai.expect(object.avgFavorite).equal(7); }) }); it("$avg fail",function(done) { - JOQULAR.match({favoriteNumbers:{$avg:"avgFavorite"}},testobject) + JOQULAR.query({favoriteNumbers:{$avg:"avgFavorite"}},testobject) .then(([object]) => { testvalidator(object); chai.expect(object.avgFavorite).equal(6); }).catch(() =>done()) }); it("$max",function() { - return JOQULAR.match({favoriteNumbers:{$max:"maxFavorite"}},testobject) + return JOQULAR.query({favoriteNumbers:{$max:"maxFavorite"}},testobject) .then(([object]) => { testvalidator(object); chai.expect(object.maxFavorite).equal(13); }) }); it("$max fail",function(done) { - JOQULAR.match({favoriteNumbers:{$max:"maxFavorite"}},testobject) + JOQULAR.query({favoriteNumbers:{$max:"maxFavorite"}},testobject) .then(([object]) => { testvalidator(object); chai.expect(object.maxFavorite).equal(7); }).catch(() =>done()) }); it("$count",function() { - return JOQULAR.match({favoriteNumbers:{$count:"countFavorite"}},testobject) + return JOQULAR.query({favoriteNumbers:{$count:"countFavorite"}},testobject) .then(([object]) => { testvalidator(object); chai.expect(object.countFavorite).equal(3); }) }); it("$count fail",function(done) { - JOQULAR.match({favoriteNumbers:{$count:"countFavorite"}},testobject) + JOQULAR.query({favoriteNumbers:{$count:"countFavorite"}},testobject) .then(([object]) => { testvalidator(object); chai.expect(object.countFavorite).equal(4); }).catch(() =>done()) }); it("$mina",function() { - return JOQULAR.match({mixedArray:{$min:"minFavorite"}},testobject) + return JOQULAR.query({mixedArray:{$min:"minFavorite"}},testobject) .then(([object]) => { testvalidator(object); chai.expect(object.minFavorite).equal(1); }) }); it("$mina fail",function(done) { - JOQULAR.match({mixedArray:{$min:"minFavorite"}},testobject) + JOQULAR.query({mixedArray:{$min:"minFavorite"}},testobject) .then(([object]) => { testvalidator(object); chai.expect(object.minFavorite).equal(2); }).catch(() =>done()) }); it("$avga",function() { - return JOQULAR.match({mixedArray:{$avga:"avg"}},testobject) + return JOQULAR.query({mixedArray:{$avga:"avg"}},testobject) .then(([object]) => { testvalidator(object); chai.expect(object.avg).equal(1); }) }); it("$avga fail",function(done) { - JOQULAR.match({mixedArray:{$avga:"avg"}},testobject) + JOQULAR.query({mixedArray:{$avga:"avg"}},testobject) .then(([object]) => { testvalidator(object); chai.expect(object.avg).equal(2); }).catch(() =>done()) }); it("$maxa",function() { - return JOQULAR.match({mixedArray:{$maxa:"max"}},testobject) + return JOQULAR.query({mixedArray:{$maxa:"max"}},testobject) .then(([object]) => { testvalidator(object); chai.expect(object.max).equal(1); }) }); it("$maxa fail",function(done) { - JOQULAR.match({mixedArray:{$maxa:"max"}},testobject) + JOQULAR.query({mixedArray:{$maxa:"max"}},testobject) .then(([object]) => { testvalidator(object); chai.expect(object.max).equal(2); }).catch(() =>done()) }); it("$counta",function() { - return JOQULAR.match({mixedArray:{$counta:"count"}},testobject) + return JOQULAR.query({mixedArray:{$counta:"count"}},testobject) .then(([object]) => { testvalidator(object); chai.expect(object.count).equal(4); }) }); it("$counta fail",function(done) { - JOQULAR.match({mixedArray:{$counta:"count"}},testobject) + JOQULAR.query({mixedArray:{$counta:"count"}},testobject) .then(([object]) => { testvalidator(object); chai.expect(object.count).equal(5); }).catch(() =>done()) }); it("$count mixed",function() { - return JOQULAR.match({mixedArray:{$count:"count"}},testobject) + return JOQULAR.query({mixedArray:{$count:"count"}},testobject) .then(([object]) => { testvalidator(object); chai.expect(object.count).equal(3); }) }); it("$as",function() { - return JOQULAR.match({size:{$as:"Size"}},testobject) + return JOQULAR.query({size:{$as:"Size"}},testobject) .then(([object]) => { chai.expect(object.Size).equal(10); }) }); - it("$compute",function() { - return JOQULAR.match({computed:{$compute:()=>1}},testobject) + it("$compute $as",function() { + return JOQULAR.query({none:{$compute:()=>1, $as:"computed"}},testobject) .then(([object]) => { testvalidator(object); chai.expect(object.computed).equal(1); }) }); it("$filter",function() { - return JOQULAR.match({mixedArray:{$filter:(item)=>item!==undefined}},testobject) + return JOQULAR.query({mixedArray:{$filter:(item)=>item!==undefined}},testobject) .then(([object]) => { testvalidator(object); chai.expect(object.mixedArray.length).equal(3); }) }); it("$sort",function() { - return JOQULAR.match({favoriteNumbers:{$sort:(a,b)=> b - a}},testobject) + return JOQULAR.query({favoriteNumbers:{$sort:(a,b)=> b - a}},testobject) .then(([object]) => { testvalidator(object); chai.expect(object.favoriteNumbers[0]).equal(13); }) }); it("$sample",function() { - return JOQULAR.match({data:{$sample:[.5,5]}},testobject) + return JOQULAR.query({data:{$sample:[.5,5]}},testobject) .then(([object]) => { testvalidator(object); chai.expect(object.data.length).equal(5); }) }); it("$echoes",function() { - return JOQULAR.match({name:{$echoes:"jo"}},testobject) + return JOQULAR.query({name:{$echoes:"jo"}},testobject) .then(([object]) => { testvalidator(object); }) }); it("$echoes fail",function(done) { - JOQULAR.match({name:{$echoes:"bill"}},testobject) + JOQULAR.query({name:{$echoes:"bill"}},testobject) .then(([object]) => { testvalidator(object); }).catch(() =>done()) }); it("$typeof",function() { - return JOQULAR.match({name:{$typeof:"string"}},testobject) + return JOQULAR.query({name:{$typeof:"string"}},testobject) .then(([object]) => { testvalidator(object); }) }); it("$typeof fail",function(done) { - JOQULAR.match({name:{$typeof:"number"}},testobject) + JOQULAR.query({name:{$typeof:"number"}},testobject) .then(([object]) => { testvalidator(object); }).catch(() =>done()) }); it("$and",function() { - return JOQULAR.match({name:{$and:{$typeof:"string",$:(value) => value.length===3}}},testobject) + return JOQULAR.query({name:{$and:{$typeof:"string",$:(value) => value.length===3}}},testobject) .then(([object]) => { testvalidator(object); }) }); it("$or",function() { - return JOQULAR.match({name:{$or:{$typeof:"string",$:(value) => value.length===3}}},testobject) + return JOQULAR.query({name:{$or:{$typeof:"string",$:(value) => value.length===3}}},testobject) .then(([object]) => { testvalidator(object); }) }); it("$xor",function(done) { - JOQULAR.match({name:{$xor:{$typeof:"string",$and:{$typeof:"number"}}}},testobject) + JOQULAR.query({name:{$xor:{$typeof:"string",$and:{$typeof:"number"}}}},testobject) .then(([object]) => { testvalidator(object); }) .catch(() =>done()) }); it("$xor fail direct",function(done) { - JOQULAR.match({name:{$xor:{$typeof:"string",$:(value) => value.length===3}}},testobject) + JOQULAR.query({name:{$xor:{$typeof:"string",$:(value) => value.length===3}}},testobject) .then(([object]) => { testvalidator(object); }) .catch(() =>done()) }); it("$xor fail indirect",function(done) { - JOQULAR.match({name:{$xor:{$typeof:"string",$and:{$typeof:"string"}}}},testobject) + JOQULAR.query({name:{$xor:{$typeof:"string",$and:{$typeof:"string"}}}},testobject) .then(([object]) => { testvalidator(object); }) .catch(() =>done()) }); it("$redact",function() { - return JOQULAR.match({unneeded:{$redact:null}},testobject) + return JOQULAR.query({unneeded:{$redact:null}},testobject) .then(([object]) => { testvalidator(object); chai.expect(object.unneeded).equal(undefined); }) }); it("$some",function() { - return JOQULAR.match({favoriteNumbers:{$some:(value) => value===13}},testobject) + return JOQULAR.query({favoriteNumbers:{$some:(value) => value===13}},testobject) .then(([object]) => { testvalidator(object); }) }); it("$some fail",function(done) { - JOQULAR.match({favoriteNumbers:{$some:(value) => value===20}},testobject) + JOQULAR.query({favoriteNumbers:{$some:(value) => value===20}},testobject) .then(([object]) => { testvalidator(object); }) .catch(() =>done()) }); it("$every",function() { - return JOQULAR.match({favoriteNumbers:{$every:(value) => value>0}},testobject) + return JOQULAR.query({favoriteNumbers:{$every:(value) => value>0}},testobject) .then(([object]) => { testvalidator(object); }) }); it("$every fail",function(done) { - JOQULAR.match({favoriteNumbers:{$every:(value) => value>13}},testobject) + JOQULAR.query({favoriteNumbers:{$every:(value) => value>13}},testobject) .then(([object]) => { testvalidator(object); }) .catch(() =>done()) }); it("$reduce",function() { - return JOQULAR.match({favoriteNumbers:{$reduce:[(accum,value) => accum += value,0,"sum"]}},testobject) + return JOQULAR.query({favoriteNumbers:{$reduce:[(accum,value) => accum += value,0,"sum"]}},testobject) .then(([object]) => { testvalidator(object); chai.expect(object.sum).equal(21); }) }); it("$map",function() { - return JOQULAR.match({favoriteNumbers:{$map:[(value) => value,"mapped"]}},testobject) + return JOQULAR.query({favoriteNumbers:{$map:[(value) => value,"mapped"]}},testobject) .then(([object]) => { testvalidator(object); chai.expect(object.mapped.length).equal(3); }) }); it("$on set",function() { let event; - return JOQULAR.match({monitored:{$on:{set:(...args) => event=args}}},testobject) + return JOQULAR.query({monitored:{$on:{set:(...args) => event=args}}},testobject) .then(([object]) => { testvalidator(object); object.monitored = true; @@ -385,7 +385,7 @@ describe("Test",function() { }); it("$on get",function() { let event; - return JOQULAR.match({monitored:{$on:{get:(...args) => event=args}}},testobject) + return JOQULAR.query({monitored:{$on:{get:(...args) => event=args}}},testobject) .then(([object]) => { testvalidator(object); const monitored = object.monitored; @@ -396,7 +396,7 @@ describe("Test",function() { }); it("$on delete",function() { let event; - return JOQULAR.match({monitored:{$on:{delete:(...args) => event=args}}},testobject) + return JOQULAR.query({monitored:{$on:{delete:(...args) => event=args}}},testobject) .then(([object]) => { testvalidator(object); delete object.monitored;