diff --git a/README.md b/README.md index 887a0a6..8751bf6 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,14 @@ 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. + +Mutation options include property aliasing and redaction, value substitution (including array summaries, sorting, filtering, etc.), adding event handlers, freezing objects and properties. + +Matching options include functional testing of both property names and values as well as over 30 built-in predicates. + +You can also use JOQULAR to validate objects. + # Installation Of Reference Implementation npm install joqular @@ -16,8 +24,8 @@ const = await JOQULAR.match(,); ``` 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 + [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 ``` For now see the [Medium article](https://medium.com/@anywhichway/joqular-high-powered-javascript-pattern-matching-273a0d77eab5) for more information. @@ -150,6 +158,8 @@ $nin $not +$on + $or $outside @@ -217,6 +227,8 @@ $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-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 cb496ac..7753b86 100644 --- a/index.js +++ b/index.js @@ -46,6 +46,7 @@ }, FUNCTIONS = {}, JOQULAR = {}, + UNDEFINED = ()=>undefined, addFunction = (f,name=f.name) => { if(!name || name==="anonymous") { throw new Error("JOQULAR.function: A function name must be provided"); @@ -76,6 +77,54 @@ } return data; }, + deepEqual = (a,b) => { + const atype = typeof(a), + btype = typeof(b); + if(atype!==btype) return false; + if((a && !b) || (!a && b)) return false; + if(a && a.length!==b.length) return false; + if(atype==="number" && isNaN(a) && isNaN(b)) return true; + if(a && atype==="object") { + if(a instanceof Date || b instanceof Date) { + if(!(a instanceof Date)) return false; + if(!(b instanceof Date)) return false; + if(a.getTime()!==b.getTime()) return false; + return true; + } + if(!Array.isArray(a) && Symbol.iterator in a) { + if(Array.isArray(b) || !(Symbol.iterator in b)) { + return false; + } + const aentries = [], + bentries = []; + a.forEach(item = aentries.push(item)); + b.forEach(item = bentries.push(item)); + return deepEqual(aentries,bentries); + } + const checked = {}, + akeys = Object.keys(a), + bkeys = Object.keys(b); + if(akeys.length!==bkeys.length) { + return false; + } + if(!akeys.every((key) => { + checked[key] = true; + return deepEqual(a[key],b[key]); + })) { + return false; + } + if(!bkeys.every((key) => { + if(checked[key]) { + return true; + } + return deepEqual(a[key],b[key]); + })) { + return false; + } + return true; + } + return a===b; + }, deleteFunction = (name) => { if(!name || name==="anonymous") { throw new Error("JOQULAR.function: A function name must be provided"); @@ -105,11 +154,22 @@ } return false; }, - match = async (pattern,value,extracted={}) => { - const copy = deepCopy(value); - return matchaux(pattern,copy,extracted) + extract = async (pattern,value) => { + const extracted = {}; + if(matchaux(pattern,value,extracted)) { + return extracted; + } }, - matchaux = async (pattern,value,extracted={},objectKey,object) => { + match = 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); + } + return results; + }, + matchaux = async (pattern,value,extracted,objectKey,object) => { if(pattern===null || pattern===value) return value; const ptype=typeof(pattern), vtype = typeof(value); @@ -173,6 +233,7 @@ } } } + if(value && value._proxy) return value._proxy; return value; } }, @@ -205,7 +266,7 @@ this[as] = value; return true; }, - $avg(iterable,as) { + $avg(iterable,as,key) { if(Symbol.iterator in Object(iterable)) { let sum = 0, count = 0; @@ -218,11 +279,11 @@ count++; } } - this[as] = sum/count; + this[as||key] = sum/count; return true; } }, - $avga(iterable,as) { + $avga(iterable,as,key) { if(Symbol.iterator in Object(iterable)) { let sum = 0, count = 0; @@ -242,7 +303,7 @@ count++; } } - this[as] = sum/count; + this[as||key] = sum/count; return true; } }, @@ -259,30 +320,30 @@ this[key] = typeof(f)==="function" ? f(value,key,this) : f; return true; }, - $count(iterable,as) { + $count(iterable,as,key) { if(Symbol.iterator in Object(iterable)) { let count = 0; for(let value of iterable) { if(value!==undefined) count++; } - this[as] = count; + this[as||key] = count; return true; } }, - $counta(iterable,as) { + $counta(iterable,as,key) { if(Symbol.iterator in Object(iterable)) { if(iterable.length!==undefined) { - this[as] = iterable.length + this[as||key] = iterable.length } else if(typeof(iterable.count)==="function") { - this[as] = iterable.count(); + this[as||key] = iterable.count(); } else if(typeof(iterable.size)==="function") { - this[as] = iterable.size(); + this[as||key] = iterable.size(); } else { let count = 0; for(let value of iterable) { count++; } - this[as] = iterable.size(); + this[as||key] = iterable.size(); } return true; } @@ -360,22 +421,15 @@ $false() { return false; }, - $filter(filterable,f,key) { - let as, result; - if(Array.isArray(f)) { - as = f[1]; - f = f[0]; - } + $filter(filterable,spec,key) { + let [f,as] = Array.isArray(spec) ? spec : [spec], + result; if(filterable && typeof(filterable.filter)==="function") { result = filterable.filter((item) => f(item)); } else { result = []; } - if(as) { - this[as] = result; - } else { - this[key] = result; - } + this[as||key] = result; return true; }, async $forDescendant(target,{pattern,$,depth=Infinity},key) { @@ -478,13 +532,9 @@ $lte(a,b) { return a <= b; }, - async $map(iterable,f,key) { + async $map(iterable,spec,key) { const results = []; - let as; - if(Array.isArray(f)) { - as = f[1]; - f = f[0]; - } + let [f,as] = Array.isArray(spec) ? spec : [spec]; if(Symbol.iterator in Object(iterable)) { let key = 0; for(let value of iterable) { @@ -496,14 +546,10 @@ key++; } } - if(as) { - this[as] = results; - } else { - this[key] = results; - } + this[as||key] = results; return true; }, - $matches(value,regexp) { + $matches(value,regexp,key) { if(value) { const match = value.matches||value.match; if(typeof(match)==="function") { @@ -518,15 +564,13 @@ } const matches = match(regexp); if(matches) { - if(as) { - this[as] = matches; - } + this[as||key] = matches; return true; } } } }, - $max(iterable,as) { + $max(iterable,as,key) { if(Symbol.iterator in Object(iterable)) { let max; for(let value of iterable) { @@ -535,11 +579,11 @@ } max = max===undefined || value > max ? value : max; } - this[as] = max; + this[as||key] = max; return true; } }, - $maxa(iterable,as) { + $maxa(iterable,as,key) { if(Symbol.iterator in Object(iterable)) { let max = -Infinity; for(let value of iterable) { @@ -557,11 +601,11 @@ max = value > max ? value : max; } } - this[as] = max; + this[as||key] = max; return true; } }, - $min(iterable,as) { + $min(iterable,as,key) { if(Symbol.iterator in Object(iterable)) { let min; for(let value of iterable) { @@ -570,11 +614,11 @@ } min = min===undefined || value < min ? value : min; } - this[as] = min; + this[as||key] = min; return true; } }, - $mina(iterable,as) { + $mina(iterable,as,key) { if(Symbol.iterator in Object(iterable)) { let min = Infinity; for(let value of iterable) { @@ -592,7 +636,7 @@ min = value < min ? value : min; } } - this[as] = min; + this[as||key] = min; return true; } }, @@ -620,6 +664,96 @@ } return true; }, + $on(value,handlers={},key) { + const onerror = handlers.onError; + let currenthandlers = {}; + if(this._proxy) { + currenthandlers = this._proxy.handlers; + } + let handler = currenthandlers[key]; + if(!handler) { + handler = currenthandlers[key] = {get:[],set:[],delete:[]}; + } + Object.keys(handler).forEach(key => { + const f = handlers[key]; + if(f) { + if(!handler[key].includes(f)) { + handler[key].push(f); + if(onerror) { + Object.defineProperty(f,"onError",{value:onerror}); + } + } + } + }); + if(value===undefined) { + this[key] = UNDEFINED; + } + const proxy = new Proxy(this,{ + get(target,property) { + let value = property==="handlers" ? currenthandlers : target[property]; + if(value===UNDEFINED) { + value = undefined; + } + if(currenthandlers[property]) { + currenthandlers[property].get.forEach((f) => { + try { + f(proxy,key,value); + } catch(e) { + if(f.onError) { + f.onError(e); + } else { + throw(e); + } + } + }); + } + return value; + }, + set(target,property,value) { + const oldvalue = target[property]===UNDEFINED ? undefined : target[property]; + let error; + if(currenthandlers[property]) { + currenthandlers[property].set.forEach((f) => { + try { + f(proxy,key,value,oldvalue); + } catch(e) { + error = e; + if(f.onError) { + error = f.onError(e); + } else { + throw(e); + } + } + }); + } + if(error) { + throw error; + } + target[property] = value; + return true; + }, + deleteProperty(target,property) { + const value = target[property]===UNDEFINED ? undefined : target[property]; + if(currenthandlers[property]) { + currenthandlers[property].delete.forEach((f) => { + try { + f(proxy,key,value); + } catch(e) { + error = e; + if(f.onError) { + error = f.onError(e); + } else { + throw(e); + } + } + }); + } + delete target[property]; + } + }); + Object.defineProperty(this,"_proxy",{configurable:true,value:proxy}); + return true; + }, async $or(a,tests,key) { const resolve = (a,pname,value) => FUNCTIONS[pname] ? FUNCTIONS[pname].call(this,a,value,key) : false; if(Array.isArray(tests)) { @@ -658,14 +792,8 @@ delete this[key]; return true; }, - async $reduce(iterable,f,key) { - let accum, - as; - if(Array.isArray(f)) { - as = f[2]; - accum = f[1]; - f = f[0]; - } + async $reduce(iterable,spec,key) { + let [f,accum,as] = Array.isArray(spec) ? spec : [spec]; if(Symbol.iterator in Object(iterable)) { let key = 0; for(let value of iterable) { @@ -677,11 +805,7 @@ key++; } } - if(as) { - this[as] = accum; - } else { - this[key] = accum; - } + this[as||key] = accum; return true; }, $return(_,value,key) { @@ -692,7 +816,8 @@ } return true; }, - $sample(iterable,[pct,max=Infinity],key) { + $sample(iterable,spec,key) { + let [pct,max=Infinity,as] = Array.isArray(spec) ? spec : [spec]; const sample = [], indexes = []; let i = 0; @@ -710,7 +835,7 @@ } } } - this[key] = sample; + this[as||key] = sample; return true; }, async $search(text,phrase) { @@ -748,25 +873,18 @@ } return false; }, - $sort(sortable,f,key) { - let as, result; - if(Array.isArray(f)) { - as = f[1]; - f = f[0]; - } + $sort(sortable,spec,key) { + let [f,as] = Array.isArray(spec) ? spec : [spec], + result; if(sortable && typeof(sortable.sort)==="function") { result = sortable.sort((a,b) => f(a,b)); } else { result = []; } - if(as) { - this[as] = result - } else { - this[key] = result; - } + this[as||key] = result return true; }, - $sum(iterable,as) { + $sum(iterable,as,key) { if(Symbol.iterator in Object(iterable)) { let sum = 0; for(let value of iterable) { @@ -777,11 +895,11 @@ sum =+ value; } } - this[as] = sum; + this[as||key] = sum; return true; } }, - $suma(iterable,as) { + $suma(iterable,as,key) { if(Symbol.iterator in Object(iterable)) { let sum = 0; for(let value of iterable) { @@ -799,7 +917,7 @@ sum =+ value; } } - this[as] = sum; + this[as||key] = sum; return true; } }, diff --git a/package.json b/package.json index 650884c..b09da48 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "joqular", - "version": "2.0.0b", - "description": "(JOQULAR - JavaScript Object Query Language Representation) For JSON data matching, transformation, and extraction", + "version": "2.0.1b", + "description": "JOQULAR (JavaScript Object Query Language Representation) for JSON data matching, transformation, validation and extraction", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" diff --git a/test/index.js b/test/index.js index fc427a3..e0a6677 100644 --- a/test/index.js +++ b/test/index.js @@ -48,325 +48,362 @@ notnow.setUTCFullYear(yr+1); describe("Test",function() { it("double property",function() { return JOQULAR.match({name:"joe",age:27},testobject) - .then(object => testvalidator(object)) + .then(([object]) => testvalidator(object)) }); it("inline property",function() { return JOQULAR.match({[key => key==="age"]:{$lt:28}},testobject) - .then(object => testvalidator(object)) + .then(([object]) => testvalidator(object)) }); it("inline value true",function() { return JOQULAR.match({age:value => value < 28},testobject) - .then(object => testvalidator(object)) + .then(([object]) => testvalidator(object)) }); it("inline value false",function() { return JOQULAR.match({age:value => value < 27},testobject) - .then(object => testvalidator(object)).catch(() => true); + .then(([object]) => testvalidator(object)).catch(() => true); }); it("functional key",function() { return JOQULAR.match({[key => key==="name"]:{$typeof:"string"}},testobject) - .then(object => testvalidator(object)) + .then(([object]) => testvalidator(object)) }); it("RegExp key",function() { return JOQULAR.match({[/.*name/]:{$eq: "joe"}},{name:"joe",age:27,race:"caucasian"}) - .then(object => testvalidator(object)) + .then(([object]) => testvalidator(object)) }); xit("$",function() { - return JOQULAR.match({name:{$:value=>value==="joe"}},testobject).then(object => { testvalidator(object); }) + return JOQULAR.match({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.match({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.match({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.match({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.match({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.match({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.match({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.match({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.match({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.match({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.match({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.match({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.match({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.match({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.match({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.match({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.match({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.match({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.match({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.match({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.match({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.match({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.match({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.match({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.match({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.match({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.match({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.match({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.match({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.match({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.match({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.match({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.match({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.match({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.match({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.match({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.match({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.match({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.match({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.match({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.match({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.match({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.match({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.match({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.match({now:{["$"+key]:notnow}},testobject).then(([object]) => testvalidator(object)).catch(() =>done()) }); } } it("$min",function() { return JOQULAR.match({favoriteNumbers:{$min:"minFavorite"}},testobject) - .then(object => { testvalidator(object); chai.expect(object.minFavorite).equal(1); }) + .then(([object]) => { testvalidator(object); chai.expect(object.minFavorite).equal(1); }) }); it("$min fail",function(done) { JOQULAR.match({favoriteNumbers:{$min:"minFavorite"}},testobject) - .then(object => { testvalidator(object); chai.expect(object.minFavorite).equal(2); }).catch(() =>done()) + .then(([object]) => { testvalidator(object); chai.expect(object.minFavorite).equal(2); }).catch(() =>done()) }); it("$avg",function() { return JOQULAR.match({favoriteNumbers:{$avg:"avgFavorite"}},testobject) - .then(object => { testvalidator(object); chai.expect(object.avgFavorite).equal(7); }) + .then(([object]) => { testvalidator(object); chai.expect(object.avgFavorite).equal(7); }) }); it("$avg fail",function(done) { JOQULAR.match({favoriteNumbers:{$avg:"avgFavorite"}},testobject) - .then(object => { testvalidator(object); chai.expect(object.avgFavorite).equal(6); }).catch(() =>done()) + .then(([object]) => { testvalidator(object); chai.expect(object.avgFavorite).equal(6); }).catch(() =>done()) }); it("$max",function() { return JOQULAR.match({favoriteNumbers:{$max:"maxFavorite"}},testobject) - .then(object => { testvalidator(object); chai.expect(object.maxFavorite).equal(13); }) + .then(([object]) => { testvalidator(object); chai.expect(object.maxFavorite).equal(13); }) }); it("$max fail",function(done) { JOQULAR.match({favoriteNumbers:{$max:"maxFavorite"}},testobject) - .then(object => { testvalidator(object); chai.expect(object.maxFavorite).equal(7); }).catch(() =>done()) + .then(([object]) => { testvalidator(object); chai.expect(object.maxFavorite).equal(7); }).catch(() =>done()) }); it("$count",function() { return JOQULAR.match({favoriteNumbers:{$count:"countFavorite"}},testobject) - .then(object => { testvalidator(object); chai.expect(object.countFavorite).equal(3); }) + .then(([object]) => { testvalidator(object); chai.expect(object.countFavorite).equal(3); }) }); it("$count fail",function(done) { JOQULAR.match({favoriteNumbers:{$count:"countFavorite"}},testobject) - .then(object => { testvalidator(object); chai.expect(object.countFavorite).equal(4); }).catch(() =>done()) + .then(([object]) => { testvalidator(object); chai.expect(object.countFavorite).equal(4); }).catch(() =>done()) }); it("$mina",function() { return JOQULAR.match({mixedArray:{$min:"minFavorite"}},testobject) - .then(object => { testvalidator(object); chai.expect(object.minFavorite).equal(1); }) + .then(([object]) => { testvalidator(object); chai.expect(object.minFavorite).equal(1); }) }); it("$mina fail",function(done) { JOQULAR.match({mixedArray:{$min:"minFavorite"}},testobject) - .then(object => { testvalidator(object); chai.expect(object.minFavorite).equal(2); }).catch(() =>done()) + .then(([object]) => { testvalidator(object); chai.expect(object.minFavorite).equal(2); }).catch(() =>done()) }); it("$avga",function() { return JOQULAR.match({mixedArray:{$avga:"avg"}},testobject) - .then(object => { testvalidator(object); chai.expect(object.avg).equal(1); }) + .then(([object]) => { testvalidator(object); chai.expect(object.avg).equal(1); }) }); it("$avga fail",function(done) { JOQULAR.match({mixedArray:{$avga:"avg"}},testobject) - .then(object => { testvalidator(object); chai.expect(object.avg).equal(2); }).catch(() =>done()) + .then(([object]) => { testvalidator(object); chai.expect(object.avg).equal(2); }).catch(() =>done()) }); it("$maxa",function() { return JOQULAR.match({mixedArray:{$maxa:"max"}},testobject) - .then(object => { testvalidator(object); chai.expect(object.max).equal(1); }) + .then(([object]) => { testvalidator(object); chai.expect(object.max).equal(1); }) }); it("$maxa fail",function(done) { JOQULAR.match({mixedArray:{$maxa:"max"}},testobject) - .then(object => { testvalidator(object); chai.expect(object.max).equal(2); }).catch(() =>done()) + .then(([object]) => { testvalidator(object); chai.expect(object.max).equal(2); }).catch(() =>done()) }); it("$counta",function() { return JOQULAR.match({mixedArray:{$counta:"count"}},testobject) - .then(object => { testvalidator(object); chai.expect(object.count).equal(4); }) + .then(([object]) => { testvalidator(object); chai.expect(object.count).equal(4); }) }); it("$counta fail",function(done) { JOQULAR.match({mixedArray:{$counta:"count"}},testobject) - .then(object => { testvalidator(object); chai.expect(object.count).equal(5); }).catch(() =>done()) + .then(([object]) => { testvalidator(object); chai.expect(object.count).equal(5); }).catch(() =>done()) }); it("$count mixed",function() { return JOQULAR.match({mixedArray:{$count:"count"}},testobject) - .then(object => { testvalidator(object); chai.expect(object.count).equal(3); }) + .then(([object]) => { testvalidator(object); chai.expect(object.count).equal(3); }) }); it("$as",function() { return JOQULAR.match({size:{$as:"Size"}},testobject) - .then(object => { chai.expect(object.Size).equal(10); }) + .then(([object]) => { chai.expect(object.Size).equal(10); }) }); it("$compute",function() { return JOQULAR.match({computed:{$compute:()=>1}},testobject) - .then(object => { testvalidator(object); chai.expect(object.computed).equal(1); }) + .then(([object]) => { testvalidator(object); chai.expect(object.computed).equal(1); }) }); it("$filter",function() { return JOQULAR.match({mixedArray:{$filter:(item)=>item!==undefined}},testobject) - .then(object => { testvalidator(object); chai.expect(object.mixedArray.length).equal(3); }) + .then(([object]) => { testvalidator(object); chai.expect(object.mixedArray.length).equal(3); }) }); it("$sort",function() { return JOQULAR.match({favoriteNumbers:{$sort:(a,b)=> b - a}},testobject) - .then(object => { testvalidator(object); chai.expect(object.favoriteNumbers[0]).equal(13); }) + .then(([object]) => { testvalidator(object); chai.expect(object.favoriteNumbers[0]).equal(13); }) }); it("$sample",function() { return JOQULAR.match({data:{$sample:[.5,5]}},testobject) - .then(object => { + .then(([object]) => { testvalidator(object); chai.expect(object.data.length).equal(5); }) }); it("$echoes",function() { return JOQULAR.match({name:{$echoes:"jo"}},testobject) - .then(object => { testvalidator(object); }) + .then(([object]) => { testvalidator(object); }) }); it("$echoes fail",function(done) { JOQULAR.match({name:{$echoes:"bill"}},testobject) - .then(object => { testvalidator(object); }).catch(() =>done()) + .then(([object]) => { testvalidator(object); }).catch(() =>done()) }); it("$typeof",function() { return JOQULAR.match({name:{$typeof:"string"}},testobject) - .then(object => { testvalidator(object); }) + .then(([object]) => { testvalidator(object); }) }); it("$typeof fail",function(done) { JOQULAR.match({name:{$typeof:"number"}},testobject) - .then(object => { testvalidator(object); }).catch(() =>done()) + .then(([object]) => { testvalidator(object); }).catch(() =>done()) }); it("$and",function() { return JOQULAR.match({name:{$and:{$typeof:"string",$:(value) => value.length===3}}},testobject) - .then(object => { testvalidator(object); }) + .then(([object]) => { testvalidator(object); }) }); it("$or",function() { return JOQULAR.match({name:{$or:{$typeof:"string",$:(value) => value.length===3}}},testobject) - .then(object => { testvalidator(object); }) + .then(([object]) => { testvalidator(object); }) }); it("$xor",function(done) { JOQULAR.match({name:{$xor:{$typeof:"string",$and:{$typeof:"number"}}}},testobject) - .then(object => { testvalidator(object); }) + .then(([object]) => { testvalidator(object); }) .catch(() =>done()) }); it("$xor fail direct",function(done) { JOQULAR.match({name:{$xor:{$typeof:"string",$:(value) => value.length===3}}},testobject) - .then(object => { testvalidator(object); }) + .then(([object]) => { testvalidator(object); }) .catch(() =>done()) }); it("$xor fail indirect",function(done) { JOQULAR.match({name:{$xor:{$typeof:"string",$and:{$typeof:"string"}}}},testobject) - .then(object => { testvalidator(object); }) + .then(([object]) => { testvalidator(object); }) .catch(() =>done()) }); it("$redact",function() { return JOQULAR.match({unneeded:{$redact:null}},testobject) - .then(object => { testvalidator(object); chai.expect(object.unneeded).equal(undefined); }) + .then(([object]) => { testvalidator(object); chai.expect(object.unneeded).equal(undefined); }) }); it("$some",function() { return JOQULAR.match({favoriteNumbers:{$some:(value) => value===13}},testobject) - .then(object => { testvalidator(object); }) + .then(([object]) => { testvalidator(object); }) }); it("$some fail",function(done) { JOQULAR.match({favoriteNumbers:{$some:(value) => value===20}},testobject) - .then(object => { testvalidator(object); }) + .then(([object]) => { testvalidator(object); }) .catch(() =>done()) }); it("$every",function() { return JOQULAR.match({favoriteNumbers:{$every:(value) => value>0}},testobject) - .then(object => { testvalidator(object); }) + .then(([object]) => { testvalidator(object); }) }); it("$every fail",function(done) { JOQULAR.match({favoriteNumbers:{$every:(value) => value>13}},testobject) - .then(object => { testvalidator(object); }) + .then(([object]) => { testvalidator(object); }) .catch(() =>done()) }); it("$reduce",function() { return JOQULAR.match({favoriteNumbers:{$reduce:[(accum,value) => accum += value,0,"sum"]}},testobject) - .then(object => { testvalidator(object); chai.expect(object.sum).equal(21); }) + .then(([object]) => { testvalidator(object); chai.expect(object.sum).equal(21); }) }); it("$map",function() { return JOQULAR.match({favoriteNumbers:{$map:[(value) => value,"mapped"]}},testobject) - .then(object => { testvalidator(object); chai.expect(object.mapped.length).equal(3); }) + .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) + .then(([object]) => { + testvalidator(object); + object.monitored = true; + chai.expect(object.monitored).equal(true); + chai.expect(event.length).equal(4); + chai.expect(event[0]).equal(object); + chai.expect(event[1]).equal("monitored"); + chai.expect(event[2]).equal(true); + chai.expect(event[3]).equal(undefined); + }) + }); + it("$on get",function() { + let event; + return JOQULAR.match({monitored:{$on:{get:(...args) => event=args}}},testobject) + .then(([object]) => { + testvalidator(object); + const monitored = object.monitored; + chai.expect(event.length).equal(3); + chai.expect(event[0]).equal(object); + chai.expect(event[1]).equal("monitored"); + }) + }); + it("$on delete",function() { + let event; + return JOQULAR.match({monitored:{$on:{delete:(...args) => event=args}}},testobject) + .then(([object]) => { + testvalidator(object); + delete object.monitored; + chai.expect(event.length).equal(3); + chai.expect(event[0]).equal(object); + chai.expect(event[1]).equal("monitored"); + chai.expect(event[2]).equal(undefined); + }) }); }); \ No newline at end of file