UNPKG

redredisearch

Version:

Redis search for node.js powered by the RediSearch module

443 lines (381 loc) 7.8 kB
/*! * redredisearch * * Forked from tj/reds * Original work Copyright(c) 2011 TJ Holowaychuk <tj@vision-media.ca> * Modified work Copyright(c) 2017 Kyle Davis * MIT Licensed */ /** * Module dependencies. */ var redis = require('redis'); function noop(){}; /** * Library version. */ exports.version = '0.0.1'; /** * Expose `Search`. */ exports.Search = Search; /** * Expose `Query`. */ exports.Query = Query; /** * Search types. */ var types = { intersect: 'and', union: 'or', and: 'and', or: 'or' }; /** * Alternate way to set client * provide your own behaviour. * * @param {RedisClient} inClient * @return {RedisClient} * @api public */ exports.setClient = function(inClient) { return exports.client = inClient; } /** * Create a redis client, override to * provide your own behaviour. * * @return {RedisClient} * @api public */ exports.createClient = function(){ return exports.client || (exports.client = redis.createClient()); }; /** * Confirm the existence of the RediSearch Redis module * * @api public */ exports.confirmModule = function(cb) { exports.client.send_command('ft.create',[], function(err) { let strMsg = String(err); if (strMsg.indexOf('ERR wrong number of arguments') > 0) { cb(null); } else { cb(err); } }); } /** * Return a new reds `Search` with the given `key`. * @param {String} key * @param {Object} opts * @return {Search} * @api public */ exports.createSearch = function(key,opts,cb){ const searchObj = function(err,info) { if (err) { cb(err); } else { cb(err,new Search(key,info,opts)); } }; opts = !opts ? {} : opts; opts.payloadField = opts.payloadField ? opts.payloadField : 'payload'; if (!key) throw new Error('createSearch() requires a redis key for namespacing'); exports.client.send_command('FT.INFO',[key],function(err,info) { if (err) { //if the index is not found, we need to make it. if (String(err).indexOf('Unknown Index name') > 0 ){ let args = [ key, 'SCHEMA', opts.payloadField, 'text' ]; exports.client.send_command( 'FT.CREATE', args, function(err) { if (err) { cb(err); } else { exports.client.send_command('FT.INFO',[key],searchObj); } } ); } } else { searchObj(err,info); } }); }; /** * Return the words in `str`. This is for compatability reasons (convert OR queries to pipes) * * @param {String} str * @return {Array} * @api private */ exports.words = function(str){ return String(str).match(/\w+/g); }; /** * Initialize a new `Query` with the given `str` * and `search` instance. * * @param {String} str * @param {Search} search * @api public */ function Query(str, search) { this.str = str; this.type('and'); this.search = search; } /** * Set `type` to "union" or "intersect", aliased as * "or" and "and". * * @param {String} type * @return {Query} for chaining * @api public */ Query.prototype.type = function(type){ if (type === 'direct') { this._directQuery = true; } else { this._direct = false; this._type = types[type]; } return this; }; /** * Limit search to the specified range of elements. * * @param {String} start * @param {String} stop * @return {Query} for chaining * @api public */ Query.prototype.between = function(start, stop){ this._start = start; this._stop = stop; return this; }; /** * Perform the query and callback `fn(err, ids)`. * * @param {Function} fn * @return {Query} for chaining * @api public */ Query.prototype.end = function(fn){ var key = this.search.key, db = this.search.client, query = this.str, direct = this._directQuery, args = [], joiner = ' ', rediSearchQuery; if (direct) { rediSearchQuery = query; } else { rediSearchQuery = exports.words(query); if (this._type === 'or') { joiner = '|' } rediSearchQuery = rediSearchQuery.join(joiner); } args = [ key, rediSearchQuery, 'NOCONTENT' ]; if (this._start) { args.push('LIMIT',this._start,this._end - this._start); } db.send_command( 'FT.SEARCH', args, function(err,resp) { if (err) { fn(err); } else { fn(err,resp.slice(1)); } } ); return this; }; /** * Initialize a new `Suggestion` with the given `key`. * * @param {String} key * @param {Object} opts * @api public */ var Suggestion = function(key,opts) { this.key = key; this.client = exports.createClient(); this.opts = opts || {}; if (this.opts.fuzzy) { this.fuzzy = opts.fuzzy; } if (this.opts.max) { this.max = opts.max; } if (this.opts.incr) { this.incr = opts.incr; } } /** * Create a new Suggestion object * * @param {String} key * @param {Object} opts * @api public */ exports.suggestionList = function(key,opts) { return new Suggestion(key,opts); } /** * Set `fuzzy` on suggestion get. Can also be set via opts in the constructor * * @param {Boolean} isFuzzy * @return {Suggestion} for chaining * @api public */ Suggestion.prototype.fuzzy = function(isFuzzy){ this.fuzzy = isFuzzy; return this; }; /** * Set the max number of returned suggestions. Can also be set via opts in the constructor * * @param {Number} maxResults * @return {Suggestion} for chaining * @api public */ Suggestion.prototype.maxResults = function(maxResults){ this.maxResults = maxResults; return this; }; Suggestion.prototype.add = function(str,score,fn) { var key = this.key; var db = this.client; var args = [ key, str, score, ]; if (this.incr) { args.push('INCR'); } db.send_command( 'FT.SUGADD', args, fn || noop ); return this; } Suggestion.prototype.get = function(prefix,fn) { var key = this.key; var db = this.client; var args = [ key, prefix ]; if (this.fuzzy) { args.push('FUZZY'); } if (this.maxResults) { args.push('MAX',this.maxResults); } db.send_command( 'FT.SUGGET', args, fn ); return this; } Suggestion.prototype.del = function(str,fn) { var key = this.key; var db = this.client; db.send_command( 'FT.SUGDEL', [ key, str ], fn ); return this; } /** * Initialize a new `Search` with the given `key`. * * @param {String} key * @api public */ function Search(key,info,opts) { this.key = key; this.client = exports.createClient(); this.opts = opts; } /** * Index the given `str` mapped to `id`. * * @param {String} str * @param {Number|String} id * @param {Function} fn * @api public */ Search.prototype.index = function(str, id, fn){ var key = this.key; var db = this.client; var opts = this.opts; db.send_command( 'FT.ADD', [ key, id, 1, //default - this should be to be set in future versions 'NOSAVE', //emulating Reds original behaviour 'REPLACE', //emulating Reds original behaviour 'FIELDS', opts.payloadField, str ], fn || noop ); return this; }; /** * Remove occurrences of `id` from the index. * * @param {Number|String} id * @api public */ Search.prototype.remove = function(id, fn){ fn = fn || noop; var key = this.key; var db = this.client; //this.removeIndex(db, id, key, fn); db.send_command( 'FT.DEL', [ key, id ], fn ) return this; }; /** * Perform a search on the given `query` returning * a `Query` instance. * * @param {String} query * @param {Query} * @api public */ Search.prototype.query = function(query){ return new Query(query, this); };