diff --git a/.npmignore b/.npmignore index c757c255..c8e55ee4 100644 --- a/.npmignore +++ b/.npmignore @@ -9,3 +9,4 @@ benchmark.js analyse.r docs/html npm-debug.log +.travis.yml diff --git a/lib/connectors/kv-memory.js b/lib/connectors/kv-memory.js index ce3342fd..f7caa879 100644 --- a/lib/connectors/kv-memory.js +++ b/lib/connectors/kv-memory.js @@ -3,6 +3,7 @@ var assert = require('assert'); var Connector = require('loopback-connector').Connector; var debug = require('debug')('loopback:connector:kv-memory'); +var minimatch = require('minimatch'); var util = require('util'); exports.initialize = function initializeDataSource(dataSource, cb) { @@ -160,8 +161,10 @@ KeyValueMemoryConnector.prototype.iterateKeys = function(modelName, filter, options, callback) { var store = this._getStoreForModel(modelName); var self = this; + var checkFilter = createMatcher(filter.match); + var keys = Object.keys(store).filter(function(key) { - return !self._removeIfExpired(modelName, key); + return !self._removeIfExpired(modelName, key) && checkFilter(key); }); debug('ITERATE KEYS %j -> %s keys', modelName, keys.length); @@ -175,6 +178,18 @@ function(modelName, filter, options, callback) { }; }; +function createMatcher(pattern) { + if (!pattern) return function matchAll() { return true; }; + + return minimatch.filter(pattern, { + nobrace: true, + noglobstar: true, + dot: true, + noext: true, + nocomment: true, + }); +} + KeyValueMemoryConnector.prototype.disconnect = function(callback) { if (this._cleanupTimer) clearInterval(this._cleanupTimer); diff --git a/lib/kvao/iterate-keys.js b/lib/kvao/iterate-keys.js index 09b19702..28c5e9da 100644 --- a/lib/kvao/iterate-keys.js +++ b/lib/kvao/iterate-keys.js @@ -9,6 +9,10 @@ var utils = require('../utils'); * @param {Object} filter An optional filter object with the following * properties: * - `match` - glob string to use to filter returned keys, e.g. 'userid.*' + * All connectors are required to support `*` and `?`. + * They may also support additional special characters that are specific + * to the backing store. + * * @param {Object} options * * @returns {AsyncIterator} An object implementing "next(cb) -> Promise" diff --git a/lib/kvao/keys.js b/lib/kvao/keys.js index ed32a3fe..f287ede8 100644 --- a/lib/kvao/keys.js +++ b/lib/kvao/keys.js @@ -13,6 +13,9 @@ var utils = require('../utils'); * @param {Object} filter An optional filter object with the following * properties: * - `match` - glob string to use to filter returned keys, e.g. 'userid.*' + * All connectors are required to support `*` and `?`. + * They may also support additional special characters that are specific + * to the backing store. * @param {Object} options * @callback callback * @param {Error=} err diff --git a/package.json b/package.json index e4e1d0a8..0b558bea 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "eslint": "^2.9.0", "inflection": "^1.6.0", "loopback-connector": "^2.1.0", + "minimatch": "^3.0.3", "node-uuid": "^1.4.2", "qs": "^3.1.0", "strong-globalize": "^2.6.2", diff --git a/test/kvao/keys.suite.js b/test/kvao/keys.suite.js index dba1a250..adb1d474 100644 --- a/test/kvao/keys.suite.js +++ b/test/kvao/keys.suite.js @@ -9,6 +9,12 @@ module.exports = function(dataSourceFactory, connectorCapabilities) { var CacheItem; beforeEach(function unpackContext() { CacheItem = helpers.givenCacheItem(dataSourceFactory); + CacheItem.sortedKeys = function(filter, options) { + return this.keys(filter, options).then(function(keys) { + keys.sort(); + return keys; + }); + }; }); it('returns all keys - Callback API', function(done) { @@ -41,10 +47,9 @@ module.exports = function(dataSourceFactory, connectorCapabilities) { return helpers.givenKeys(AnotherModel, ['otherKey1', 'otherKey2']); }) .then(function() { - return CacheItem.keys(); + return CacheItem.sortedKeys(); }) .then(function(keys) { - keys.sort(); should(keys).eql(['key1', 'key2']); }); }); @@ -53,16 +58,48 @@ module.exports = function(dataSourceFactory, connectorCapabilities) { var expectedKeys = []; for (var ix = 0; ix < 1000; ix++) expectedKeys.push('key-' + ix); + expectedKeys.sort(); return helpers.givenKeys(CacheItem, expectedKeys) .then(function() { - return CacheItem.keys(); + return CacheItem.sortedKeys(); }) .then(function(keys) { - keys.sort(); - expectedKeys.sort(); should(keys).eql(expectedKeys); }); }); + + context('with "filter.match"', function() { + beforeEach(function createTestData() { + return helpers.givenKeys(CacheItem, [ + 'hallo', + 'hello', + 'hxllo', + 'hllo', + 'heeello', + 'foo', + 'bar', + ]); + }); + + it('supports "?" operator', function() { + return CacheItem.sortedKeys({ match: 'h?llo' }).then(function(keys) { + should(keys).eql(['hallo', 'hello', 'hxllo']); + }); + }); + + it('supports "*" operator', function() { + return CacheItem.sortedKeys({ match: 'h*llo' }).then(function(keys) { + should(keys).eql(['hallo', 'heeello', 'hello', 'hllo', 'hxllo']); + }); + }); + + it('handles no matches found', function() { + return CacheItem.sortedKeys({ match: 'not-found' }) + .then(function(keys) { + should(keys).eql([]); + }); + }); + }); }); };