tame-search
premise
key/value in-memory pub/sub store that allows for a strict wildcard scheme. useful for a searchable subscription store for a pub/sub system. Essentially the system stores wildcard subscriptions, and is able to match them to incoming queries,
installation
npm i tame-search --save
example
var opts = {
searchCache:1000,
permutationCache:1000
};
var tameSearch = require('tame-search').create(opts);
tameSearch.subscribe('test-subscriber', '/test/*/*', {ref: 'my reference id: 0'});
tameSearch.subscribe('test-subscriber', '/test/*/*', {ref: 'my reference id: 1'});
tameSearch.subscribe('test-subscriber', '/test/1/*', {ref: 'my reference id: 2'});
var results = tameSearch.search('/test/1/2');
results = tameSearch.search('/test/1/2', {filter:{ref: 'my reference id: 2'}});
tameSearch.unsubscribe('test-subscriber', '/test/*/*', {filter:{ref: 'my reference id: 2'}});
results = tameSearch.search('/test/1/2');
tameSearch.unsubscribe('test-subscriber', '/test/*/*');
results = tameSearch.search('/test/1/2');
subscribe to any topic
it is possible to subscribe to any topic, using the subscribeAny method
var tameSearch = new TameSearch();
tameSearch.subscribeAny('test-subscriber', {ref: 1, topLevelRef:2});
tameSearch.subscribeAny('test-subscriber', {ref: 2, topLevelRef:1});
tameSearch.subscribe('test-subscriber', '/test/1', {ref: 3, topLevelRef:1});
tameSearch.subscribe('test-subscriber', '/test/2', {ref: 4, topLevelRef:2});
tameSearch.search('/test/1', {filter: {topLevelRef: 1}});
tameSearch.unsubscribeAny('test-subscriber', {filter:{topLevelRef:1}});
tameSearch.search('/test/1', {filter: {topLevelRef: 1}});
search all items
as opposed to the regular search it is possible to find all matching subscriptions without using the path as the filter, but rather using the filter option, this will return all items that match the filter, regardless of their subscription path
var tameSearch = new TameSearch();
tameSearch.subscribe('test-subscriber', '/test/*', {ref: 1, topLevelRef:2});
tameSearch.subscribe('test-subscriber', '/test/*', {ref: 2, topLevelRef:1});
tameSearch.subscribe('test-subscriber', '/test/*', {ref: 3, topLevelRef:2});
tameSearch.subscribe('test-subscriber', '/test/*/*', {ref: 4, topLevelRef:1});
tameSearch.subscribe('test-subscriber', '/blah/*/*', {ref: 5, topLevelRef:1});
tameSearch.subscribe('test-subscriber', '/etc/*/*', {ref: 6, topLevelRef:2});
tameSearch.subscribe('test-subscriber', '/etc/*/*', {ref: 7, topLevelRef:2});
tameSearch.searchAll({filter: {topLevelRef: 2}})
unsubscribe with returnRemoved:
by default unsubscribe just returns the count of items removed from the subscriptions, add the returnRemoved:true option to return items removed from the subscription list
var tameSearch = new TameSearch();
tameSearch.subscribe('test-subscriber', '/test/*', {ref: 1});
tameSearch.subscribe('test-subscriber', '/test/*', {ref: 2});
tameSearch.subscribe('test-subscriber', '/test/*', {ref: 3});
tameSearch.search('/test/2', {filter: {ref: 3}});
tameSearch.unsubscribe('/test/*', {filter: {ref: 1}, returnRemoved:true});
variable depth trailing wildcards:
if a trailing wildcard is a double (glob style) the subscription will be for made for further levels of the topic up to a specific depth
var tameSearch = TameSearch.create({defaultVariableDepth:5});
tameSearch.subscribe('test-subscriber', '/test/1/**', {test:'data'}, {depth:6});
tameSearch.subscribe('test-subscriber', '/test/1/*', {test:'data'});
tameSearch.subscribe('test-subscriber', '/test/1/*/*', {test:'data'});
tameSearch.subscribe('test-subscriber', '/test/1/*/*/*', {test:'data'});
tameSearch.subscribe('test-subscriber', '/test/1/*/*/*/*', {test:'data'});
tameSearch.subscribe('test-subscriber', '/test/1/*/*/*/*/*', {test:'data'});
tameSearch.subscribe('test-subscriber', '/test/1/*/*/*/*/*/*', {test:'data'});
tameSearch.subscribe('test-subscriber', '/test/1/**', {test:'data'});
tameSearch.subscribe('test-subscriber', '/test/1/*', {test:'data'});
tameSearch.subscribe('test-subscriber', '/test/1/*/*', {test:'data'});
tameSearch.subscribe('test-subscriber', '/test/1/*/*/*', {test:'data'});
tameSearch.subscribe('test-subscriber', '/test/1/*/*/*/*', {test:'data'});
tameSearch.subscribe('test-subscriber', '/test/1/*/*/*/*/*', {test:'data'});
tameSearch.unsubscribe('test-subscriber', '/test/1/**', {depth:6});
tameSearch.unsubscribe('test-subscriber', '/test/1/*');
tameSearch.unsubscribe('test-subscriber', '/test/1/*/*');
tameSearch.unsubscribe('test-subscriber', '/test/1/*/*/*');
tameSearch.unsubscribe('test-subscriber', '/test/1/*/*/*/*');
tameSearch.unsubscribe('test-subscriber', '/test/1/*/*/*/*/*');
tameSearch.unsubscribe('test-subscriber', '/test/1/*/*/*/*/*/*');
tameSearch.subscribe('test-subscriber', '/test/1/**', {test:'data', key:1});
tameSearch.subscribe('test-subscriber', '/test/1/**', {test:'data', key:2});
tameSearch.unsubscribe('test-subscriber', '/test/1/**', {filter:{key:1}});
unsubscribe from all paths:
you can unsubscribe from all paths, using a filter
var tameSearch = new TameSearch();
tameSearch.subscribe('test-subscriber', '/test/*', {ref: 1, topLevelRef:1});
tameSearch.subscribe('test-subscriber', '/test/*/*', {ref: 2, topLevelRef:1});
tameSearch.subscribe('test-subscriber', '/test/*/*/*', {ref: 3, topLevelRef:2});
tameSearch.unsubscribeAll('test-subscriber', {filter:{topLevelRef:1}, returnRemoved:true})
rules / caveats
NB: a leading / is ignored when subscribing, unsubscribing and searching:
tameSearch.subscribe('test-subscriber', 'test/1/*/3', {ref:1});
tameSearch.subscribe('test-subscriber', '/test/1/*/3', {ref:2});
tameSearch.search('/test/1/*/3')
difference between unsubscribeAny and unsubscribeAll
This could be confusing: unsubscribeAny removes specifically "any" subscriptions and ignores topic based ones, unsubscribeAll will remove topic and "any" type subscriptions
var tameSearch = new TameSearch();
tameSearch.subscribeAny('test-subscriber', {ref: 1, topLevelRef:2});
tameSearch.subscribeAny('test-subscriber', {ref: 5, topLevelRef:2});
tameSearch.subscribeAny('test-subscriber', {ref: 2, topLevelRef:1});
tameSearch.subscribe('test-subscriber', '/test/1', {ref: 3, topLevelRef:1});
tameSearch.subscribe('test-subscriber', '/test/2', {ref: 4, topLevelRef:2});
tameSearch.search('/test/1', {filter: {topLevelRef: 1}});
tameSearch.unsubscribeAny('test-subscriber', {filter:{topLevelRef:1}});
tameSearch.search('/test/1', {filter: {topLevelRef: 1}});
tameSearch.search('/test/2', {filter: {topLevelRef: 2}});
tameSearch.unsubscribeAll('test-subscriber', {filter:{topLevelRef:2}});
tameSearch.search('/test/2', {filter: {topLevelRef: 2}});
comparison is done only on paths that have a matching number of / segment dividers, ie:
tameSearch.search('/test/1');
wildcards mean nothing in the search string (for now)
tameSearch.search('/test/*'); //will not return the subscription "/test/1" or "/test/2", only "/test/*" because the paths match
subscriberKey argument is required for subscribe and unsubscribe methods, and subscription reference data must be an object
tameSearch.subscribe(undefined, '/test/1/*/*', { test : 'data' })
tameSearch.subscribe('test-subscriber', '/test/1/*/*')
tameSearch.subscribe('test-subscriber', '/test/1/*/*','string value')
tameSearch.subscribe('/test/1/*/*',{ value:'string value' })