flo
Version:
Redis powered node.js autocompleter inspired by soulmate
290 lines (260 loc) • 9.01 kB
JavaScript
// Generated by CoffeeScript 1.3.1
(function() {
var Connection, Helper, async, connectToRedis, _,
__slice = [].slice;
_ = require("underscore");
async = require("async");
exports.connect = function(options) {
return new exports.Connection(options || {});
};
Connection = (function() {
Connection.name = 'Connection';
function Connection(options) {
this.helper = new Helper;
this.redis = options.redis || connectToRedis(options);
this.namespace = options.namespace || 'flo';
this.mincomplete = options.mincomplete || 1;
if (options.database != null) {
this.redis.select(options.database);
}
}
Connection.prototype.prefixes_for_phrase = function(phrase) {
var words,
_this = this;
words = this.helper.normalize(phrase).split(' ');
return _.uniq(_.flatten(_.map(words, function(w) {
var _i, _ref, _ref1, _results;
return _.map((function() {
_results = [];
for (var _i = _ref = _this.mincomplete - 1, _ref1 = w.length - 1; _ref <= _ref1 ? _i <= _ref1 : _i >= _ref1; _ref <= _ref1 ? _i++ : _i--){ _results.push(_i); }
return _results;
}).apply(this), function(l) {
return w.slice(0, l + 1 || 9e9);
});
})));
};
Connection.prototype.search_term = function() {
var args, callback, limit, phrase, types,
_this = this;
types = arguments[0], phrase = arguments[1], args = 3 <= arguments.length ? __slice.call(arguments, 2) : [];
if (typeof args[0] === 'number') {
limit = args[0];
} else {
limit = 5;
}
callback = args[args.length - 1];
return async.map(types, function(type, callb) {
var cachekey, words;
words = _.uniq(_this.helper.normalize(phrase).split(' ')).sort();
cachekey = _this.key(type, "cache", words.join('|'));
return async.waterfall([
(function(cb) {
return _this.redis.exists(cachekey, cb);
}), (function(exists, cb) {
var interkeys, _ref;
if (!exists) {
interkeys = _.map(words, function(w) {
return _this.key(type, "index", w);
});
return (_ref = _this.redis).zinterstore.apply(_ref, [cachekey, interkeys.length].concat(__slice.call(interkeys), [function(err, count) {
return _this.redis.expire(cachekey, 10 * 60, function() {
return cb();
});
}]));
} else {
return cb();
}
}), (function(cb) {
return _this.redis.zrevrange(cachekey, 0, limit - 1, function(err, ids) {
var _ref;
if (ids.length > 0) {
return (_ref = _this.redis).hmget.apply(_ref, [_this.key(type, "data")].concat(__slice.call(ids), [cb]));
} else {
return cb(null, []);
}
});
})
], function(err, results) {
var data;
data = {};
data[type] = results;
return callb(err, data);
});
}, function(err, results) {
results = _.extend.apply(_, results);
results.term = phrase;
return callback(err, results);
});
};
Connection.prototype.add_term = function() {
var args, callback, data, id, score, term, type,
_this = this;
type = arguments[0], id = arguments[1], term = arguments[2], score = arguments[3], args = 5 <= arguments.length ? __slice.call(arguments, 4) : [];
if (typeof args[0] !== 'function') {
data = args[0];
callback = args[args.length - 1];
} else if (typeof args[0] === 'function') {
callback = args[0];
}
return async.parallel([
(function(callb) {
return _this.redis.hset(_this.key(type, "data"), id, JSON.stringify({
id: id,
term: term,
score: score,
data: data || []
}), function() {
return callb();
});
}), (function(callb) {
return async.forEach(_this.prefixes_for_phrase(term), (function(w, cb) {
return _this.redis.zadd(_this.key(type, "index", w), score, id, function() {
return cb();
});
}), function() {
return callb();
});
}), (function(callb) {
var key;
key = _this.key(type, _this.helper.normalize(term));
return _this.redis.get(key, function(err, result) {
var arr;
if (err) {
return callb(err);
}
if (result) {
arr = JSON.parse(result);
arr.push(id);
arr = _.uniq(arr);
} else {
arr = [id];
}
return _this.redis.set(key, JSON.stringify(arr), callb);
});
})
], function() {
if (callback != null) {
return callback();
}
});
};
Connection.prototype.remove_term = function(type, id, callback) {
var _this = this;
return this.redis.hget(this.key(type, "data"), id, function(err, result) {
var term;
if (err) {
return callback(err);
}
if (result === null) {
return callback(new Error("Invalid term id"));
}
term = JSON.parse(result).term;
return async.parallel([
(function(callb) {
return _this.redis.hdel(_this.key(type, "data"), id, callb);
}), (function(callb) {
return async.forEach(_this.prefixes_for_phrase(term), (function(w, cb) {
return _this.redis.zrem(_this.key(type, "index", w), id, function() {
return cb();
});
}), callb);
}), (function(callb) {
var key;
key = _this.key(type, _this.helper.normalize(term));
return _this.redis.get(key, function(err, result) {
var arr;
if (err) {
return callb(err);
}
if (result === null) {
return cb(new Error("Couldn't delete " + id(+". No such entry.")));
}
arr = JSON.parse(result);
if (arr.toString() === [id].toString()) {
return _this.redis.del(key, callb);
}
return _this.redis.set(key, JSON.stringify(_.without(arr, id)), callb);
});
})
], function(err) {
if (callback != null) {
return callback(err);
}
});
});
};
Connection.prototype.get_ids = function(type, term, callback) {
return this.redis.get(this.key(type, this.helper.normalize(term)), function(err, result) {
var arr;
if (err) {
return callback(err);
}
arr = JSON.parse(result);
if (arr === null) {
return callback(null, []);
}
return callback(null, arr);
});
};
Connection.prototype.get_data = function(type, id, callback) {
return this.redis.hget(this.key(type, "data"), id, function(err, result) {
if (err) {
return callback(err);
}
return callback(null, JSON.parse(result));
});
};
Connection.prototype.redis = function() {
return this.redis;
};
Connection.prototype.end = function() {
return this.redis.quit();
};
Connection.prototype.key = function() {
var args;
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
args.unshift(this.namespace);
return args.join(":");
};
return Connection;
})();
Helper = (function() {
Helper.name = 'Helper';
function Helper() {}
Helper.prototype.normalize = function(term) {
return this.strip(this.gsub(term.toLowerCase(), /[^a-z0-9 ]/i, ''));
};
Helper.prototype.gsub = function(source, pattern, replacement) {
var match, result;
if (!((pattern != null) && (replacement != null))) {
return source;
}
result = '';
while (source.length > 0) {
if ((match = source.match(pattern))) {
result += source.slice(0, match.index);
result += replacement;
source = source.slice(match.index + match[0].length);
} else {
result += source;
source = '';
}
}
return result;
};
Helper.prototype.strip = function(source) {
return source.replace(/^\s+/, '').replace(/\s+$/, '');
};
return Helper;
})();
connectToRedis = function(options) {
var redis;
redis = require('redis').createClient(options.port, options.host);
if (options.password != null) {
redis.auth(options.password);
}
return redis;
};
exports.Helper = new Helper;
exports.Connection = Connection;
}).call(this);