diff --git a/lib/index.js b/lib/index.js index 77ddb6b..85a92c1 100644 --- a/lib/index.js +++ b/lib/index.js @@ -47,6 +47,24 @@ function get(obj, key) { return isFunction(obj.get) ? obj.get(key) : obj[key]; } +function identity(value) { + return value; +} + +function promiseAll(promises) { + return Promise.all(promises).then(function(results) { + return !results.some(function(result) { + return !result; + }); + }); +} + +function promiseSome(promises) { + return Promise.all(promises).then(function(results) { + return results.some(identity); + }); +} + /** */ @@ -177,11 +195,26 @@ var defaultExpressions = { */ $or: function $or(a, b, k, o) { + var thenableResults = void 0; for (var i = 0, n = a.length; i < n; i++) { - if (validate(get(a, i), b, k, o)) { - return true; + var result = validate(get(a, i), b, k, o); + if (result && result.then && !thenableResults) { + thenableResults = []; + } + + if (thenableResults) { + thenableResults.push(result); + } else { + if (result) { + return true; + } } } + + if (thenableResults) { + return promiseSome(thenableResults); + } + return false; }, @@ -196,11 +229,27 @@ var defaultExpressions = { */ $and: function $and(a, b, k, o) { + var thenableResults = void 0; + for (var i = 0, n = a.length; i < n; i++) { - if (!validate(get(a, i), b, k, o)) { - return false; + var result = validate(get(a, i), b, k, o); + if (result && result.then && !thenableResults) { + thenableResults = []; } + + if (thenableResults) { + thenableResults.push(result); + } else { + if (!result) { + return false; + } + } + } + + if (thenableResults) { + return promiseAll(thenableResults); } + return true; }, @@ -223,7 +272,7 @@ var defaultExpressions = { $elemMatch: function $elemMatch(a, b, k, o) { if (isArray(b)) { - return !!~search(b, a); + return search(b, a) !== -1; } return validate(a, b, k, o); }, @@ -236,6 +285,29 @@ var defaultExpressions = { } }; +function castTesterAsFn(a, comparable, compare) { + if (a instanceof RegExp) { + return function(b) { + return typeof b === "string" && a.test(b); + }; + } else if (a instanceof Function) { + return a; + } else if (isArray(a) && !a.length) { + // Special case of a == [] + return function(b) { + return isArray(b) && !b.length; + }; + } else if (a === null) { + return function(b) { + //will match both null and undefined + return b == null; + }; + } + return function(b) { + return compare(comparable(b), comparable(a)) === 0; + }; +} + /** */ @@ -247,26 +319,7 @@ var prepare = { var comparable = _ref.comparable, compare = _ref.compare; - if (a instanceof RegExp) { - return or(function(b) { - return typeof b === "string" && a.test(b); - }); - } else if (a instanceof Function) { - return or(a); - } else if (isArray(a) && !a.length) { - // Special case of a == [] - return or(function(b) { - return isArray(b) && !b.length; - }); - } else if (a === null) { - return or(function(b) { - //will match both null and undefined - return b == null; - }); - } - return or(function(b) { - return compare(comparable(b), comparable(a)) === 0; - }); + return or(castTesterAsFn(a, comparable, compare)); }, $gt: function $gt(a, query, _ref2) { @@ -305,17 +358,21 @@ var prepare = { }, $in: function $in(a, query, options) { - var comparable = options.comparable; + var comparable = options.comparable, + compare = options.compare; return function(b) { - if (b instanceof Array) { + var thenableResults = void 0; + + if (isArray(b)) { for (var i = b.length; i--; ) { - if (~a.indexOf(comparable(get(b, i)))) { + if (a.indexOf(comparable(get(b, i))) !== -1) { return true; } } } else { var comparableB = comparable(b); + if ( comparableB === b && (typeof b === "undefined" ? "undefined" : _typeof(b)) === "object" @@ -343,9 +400,18 @@ var prepare = { Handles the case of {'field': {$in: [/regexp1/, /regexp2/, ...]}} */ for (var i = a.length; i--; ) { + var tester = castTesterAsFn(get(a, i), comparable, compare); + var validator = createRootValidator(get(a, i), options); - var result = validate(validator, b, i, a); - if ( + var result = validate(validator, comparableB, i, a); + + if (result && result.then && !thenableResults) { + thenableResults = []; + } + + if (thenableResults != null) { + thenableResults.push(result); + } else if ( result && String(result) !== "[object Object]" && String(b) !== "[object Object]" @@ -354,7 +420,9 @@ var prepare = { } } - return !!~a.indexOf(comparableB); + if (thenableResults) { + return promiseSome(thenableResults); + } } return false; @@ -453,7 +521,7 @@ var prepare = { */ $exists: function $exists(a) { - return !!a; + return Boolean(a); } }; diff --git a/src/index.js b/src/index.js index 1f12633..77fd577 100644 --- a/src/index.js +++ b/src/index.js @@ -24,6 +24,22 @@ function get(obj, key) { return isFunction(obj.get) ? obj.get(key) : obj[key]; } +function identity(value) { + return value; +} + +function promiseAll(promises) { + return Promise.all(promises).then(results => { + return !results.some(result => !result); + }); +} + +function promiseSome(promises) { + return Promise.all(promises).then(results => { + return results.some(identity); + }); +} + /** */ @@ -154,11 +170,26 @@ var defaultExpressions = { */ $or: function(a, b, k, o) { + let thenableResults; for (var i = 0, n = a.length; i < n; i++) { - if (validate(get(a, i), b, k, o)) { - return true; + const result = validate(get(a, i), b, k, o); + if (result && result.then && !thenableResults) { + thenableResults = []; + } + + if (thenableResults) { + thenableResults.push(result); + } else { + if (result) { + return true; + } } } + + if (thenableResults) { + return promiseSome(thenableResults); + } + return false; }, @@ -173,11 +204,27 @@ var defaultExpressions = { */ $and: function(a, b, k, o) { + let thenableResults; + for (var i = 0, n = a.length; i < n; i++) { - if (!validate(get(a, i), b, k, o)) { - return false; + const result = validate(get(a, i), b, k, o); + if (result && result.then && !thenableResults) { + thenableResults = []; + } + + if (thenableResults) { + thenableResults.push(result); + } else { + if (!result) { + return false; + } } } + + if (thenableResults) { + return promiseAll(thenableResults); + } + return true; }, @@ -200,7 +247,7 @@ var defaultExpressions = { $elemMatch: function(a, b, k, o) { if (isArray(b)) { - return !!~search(b, a); + return search(b, a) !== -1; } return validate(a, b, k, o); }, @@ -213,6 +260,29 @@ var defaultExpressions = { } }; +function castTesterAsFn(a, comparable, compare) { + if (a instanceof RegExp) { + return function(b) { + return typeof b === "string" && a.test(b); + }; + } else if (a instanceof Function) { + return a; + } else if (isArray(a) && !a.length) { + // Special case of a == [] + return function(b) { + return isArray(b) && !b.length; + }; + } else if (a === null) { + return function(b) { + //will match both null and undefined + return b == null; + }; + } + return function(b) { + return compare(comparable(b), comparable(a)) === 0; + }; +} + /** */ @@ -221,26 +291,7 @@ var prepare = { */ $eq: function(a, query, { comparable, compare }) { - if (a instanceof RegExp) { - return or(function(b) { - return typeof b === "string" && a.test(b); - }); - } else if (a instanceof Function) { - return or(a); - } else if (isArray(a) && !a.length) { - // Special case of a == [] - return or(function(b) { - return isArray(b) && !b.length; - }); - } else if (a === null) { - return or(function(b) { - //will match both null and undefined - return b == null; - }); - } - return or(function(b) { - return compare(comparable(b), comparable(a)) === 0; - }); + return or(castTesterAsFn(a, comparable, compare)); }, $gt: function(a, query, { comparable, compare }) { @@ -267,16 +318,19 @@ var prepare = { }, $in: function(a, query, options) { - const { comparable } = options; + const { comparable, compare } = options; return function(b) { - if (b instanceof Array) { + let thenableResults; + + if (isArray(b)) { for (var i = b.length; i--; ) { - if (~a.indexOf(comparable(get(b, i)))) { + if (a.indexOf(comparable(get(b, i))) !== -1) { return true; } } } else { var comparableB = comparable(b); + if (comparableB === b && typeof b === "object") { for (var i = a.length; i--; ) { if (String(a[i]) === String(b) && String(b) !== "[object Object]") { @@ -301,9 +355,18 @@ var prepare = { Handles the case of {'field': {$in: [/regexp1/, /regexp2/, ...]}} */ for (var i = a.length; i--; ) { + var tester = castTesterAsFn(get(a, i), comparable, compare); + var validator = createRootValidator(get(a, i), options); - var result = validate(validator, b, i, a); - if ( + var result = validate(validator, comparableB, i, a); + + if (result && result.then && !thenableResults) { + thenableResults = []; + } + + if (thenableResults != null) { + thenableResults.push(result); + } else if ( result && String(result) !== "[object Object]" && String(b) !== "[object Object]" @@ -312,7 +375,9 @@ var prepare = { } } - return !!~a.indexOf(comparableB); + if (thenableResults) { + return promiseSome(thenableResults); + } } return false; @@ -411,7 +476,7 @@ var prepare = { */ $exists: function(a) { - return !!a; + return Boolean(a); } }; diff --git a/test/async-test.js b/test/async-test.js index ca92d33..045f9df 100644 --- a/test/async-test.js +++ b/test/async-test.js @@ -7,31 +7,68 @@ describe(__filename + "#", () => { "can use a simple async $eq filter", { $eq: function(value) { - return new Promise(function(resolve) { - resolve(value > 2); - }); + return value > 2; } }, [1, 2, 3, 4, 5], [3, 4, 5] - ] + ], + + [ + "can use a simple async $in or filter", + { + $in: [ + function() { + return 5; + }, + function() { + return Promise.resolve(2); + } + ] + }, + [1, 2, 3, 4, 5], + [2, 5] + ], - // [ - // "can use a simple async $or filter", - // { - // $and: [ - // function(value) { - // new Promise(function(resolve) { - // resolve(value > 2); - // }) - // }, - // function(value) { - // new Promise(function(resolve) { - // resolve(value < 5); - // }) - // } - // ] - // }, [1, 2, 3, 4, 5], [3, 4]] + [ + "can use a simple async $and filter", + { + $and: [ + function(value) { + return new Promise(function(resolve) { + resolve(value > 2); + }); + }, + function(value) { + return new Promise(function(resolve) { + resolve(value < 5); + }); + } + ] + }, + [1, 2, 3, 4, 5], + [3, 4] + ], + + [ + "can use a simple async $or filter", + { + $or: [ + function(value) { + return new Promise(function(resolve) { + resolve(value !== 1); + }); + }, + function(value) { + return new Promise(function(resolve) { + resolve(value < 5 && value !== 1); + }); + } + ] + }, + [1, 2, 3, 4, 5], + [2, 3, 4, 5] + ] ].forEach(function([description, query, values, result]) { it(description, function() { return new Promise(function(resolve, reject) { diff --git a/test/operations-test.js b/test/operations-test.js index c853211..b78030c 100644 --- a/test/operations-test.js +++ b/test/operations-test.js @@ -475,5 +475,27 @@ describe(__filename + "#", function() { JSON.stringify(matchArray) ); }); + + it("async " + i + ": " + JSON.stringify(filter), function() { + const asyncFilter = makeQueryAync(filter); + Promise.all(array.filter(sift(asyncFilter))).then(results => { + assert.equal(results, JSON.stringify(matchArray)); + }); + }); }); }); + +const makeQueryAync = query => { + if (Array.isArray(query)) { + return query.map(makeQueryAync); + } else if (query && query.constructor === Object) { + const newQuery = {}; + for (const key in query) { + newQuery[key] = Promise.resolve(query[key]); + } + } else if (typeof query === "function") { + return query; + } + + return Promise.resolve(query); +};