Node redis (#13500)

* refactor: start migrating to node-redis

* few more zset fixes

* fix: db.scan

* fix: list methods

* fix set methods

* fix: hash methods

* use hasOwn, remove cloning

* sorted set fixes

* fix: so data is converted to strings before saving

otherwise node-redis throws below error
TypeError: "arguments[2]" must be of type "string | Buffer", got number instead.

* chore: remove comments

* fix: zrank string param

* use new close

* chore: up dbsearch

* test: add log

* test: more log

* test: log failing test

* test: catch errors in formatApiResponse

add await so exception goes to catch

* tetst: add log

* fix: dont set null/undefined values

* test: more fixes
This commit is contained in:
Barış Uşaklı
2025-06-18 13:04:57 -04:00
committed by GitHub
parent 3f7d415744
commit 14043ab0fd
21 changed files with 307 additions and 305 deletions

View File

@@ -98,7 +98,7 @@
"nconf": "0.13.0", "nconf": "0.13.0",
"nodebb-plugin-2factor": "7.5.10", "nodebb-plugin-2factor": "7.5.10",
"nodebb-plugin-composer-default": "10.2.51", "nodebb-plugin-composer-default": "10.2.51",
"nodebb-plugin-dbsearch": "6.2.19", "nodebb-plugin-dbsearch": "6.3.0",
"nodebb-plugin-emoji": "6.0.3", "nodebb-plugin-emoji": "6.0.3",
"nodebb-plugin-emoji-android": "4.1.1", "nodebb-plugin-emoji-android": "4.1.1",
"nodebb-plugin-markdown": "13.2.1", "nodebb-plugin-markdown": "13.2.1",
@@ -122,7 +122,7 @@
"postcss-clean": "1.2.0", "postcss-clean": "1.2.0",
"progress-webpack-plugin": "1.0.16", "progress-webpack-plugin": "1.0.16",
"prompt": "1.3.0", "prompt": "1.3.0",
"ioredis": "5.6.1", "redis": "5.5.6",
"rimraf": "6.0.1", "rimraf": "6.0.1",
"rss": "1.2.2", "rss": "1.2.2",
"rtlcss": "4.3.0", "rtlcss": "4.3.0",

View File

@@ -132,7 +132,6 @@ ActivityPub.resolveInboxes = async (ids) => {
}, [[], []]); }, [[], []]);
const categoryData = await categories.getCategoriesFields(cids, ['inbox', 'sharedInbox']); const categoryData = await categories.getCategoriesFields(cids, ['inbox', 'sharedInbox']);
const userData = await user.getUsersFields(uids, ['inbox', 'sharedInbox']); const userData = await user.getUsersFields(uids, ['inbox', 'sharedInbox']);
currentIds.forEach((id) => { currentIds.forEach((id) => {
if (cids.includes(id)) { if (cids.includes(id)) {
const data = categoryData[cids.indexOf(id)]; const data = categoryData[cids.indexOf(id)];

View File

@@ -145,14 +145,15 @@ Controller.postInbox = async (req, res) => {
const method = String(req.body.type).toLowerCase(); const method = String(req.body.type).toLowerCase();
if (!activitypub.inbox.hasOwnProperty(method)) { if (!activitypub.inbox.hasOwnProperty(method)) {
winston.warn(`[activitypub/inbox] Received Activity of type ${method} but unable to handle. Ignoring.`); winston.warn(`[activitypub/inbox] Received Activity of type ${method} but unable to handle. Ignoring.`);
console.log('[activitypub/inbox] method not found', method, req.body);
return res.sendStatus(200); return res.sendStatus(200);
} }
try { try {
await activitypub.inbox[method](req); await activitypub.inbox[method](req);
await activitypub.record(req.body); await activitypub.record(req.body);
helpers.formatApiResponse(202, res); await helpers.formatApiResponse(202, res);
} catch (e) { } catch (e) {
helpers.formatApiResponse(500, res, e); helpers.formatApiResponse(500, res, e).catch(err => winston.error(err.stack));
} }
}; };

View File

@@ -448,6 +448,10 @@ helpers.getHomePageRoutes = async function (uid) {
}; };
helpers.formatApiResponse = async (statusCode, res, payload) => { helpers.formatApiResponse = async (statusCode, res, payload) => {
if (!res.hasOwnProperty('req')) {
console.log('formatApiResponse', statusCode, payload);
}
if (res.req.method === 'HEAD') { if (res.req.method === 'HEAD') {
return res.sendStatus(statusCode); return res.sendStatus(statusCode);
} }

View File

@@ -61,7 +61,7 @@ redisModule.checkCompatibilityVersion = function (version, callback) {
}; };
redisModule.close = async function () { redisModule.close = async function () {
await redisModule.client.quit(); await redisModule.client.close();
if (redisModule.objectCache) { if (redisModule.objectCache) {
redisModule.objectCache.reset(); redisModule.objectCache.reset();
} }

View File

@@ -1,7 +1,7 @@
'use strict'; 'use strict';
const nconf = require('nconf'); const nconf = require('nconf');
const Redis = require('ioredis'); const { createClient, createCluster, createSentinel } = require('redis');
const winston = require('winston'); const winston = require('winston');
const connection = module.exports; const connection = module.exports;
@@ -13,28 +13,40 @@ connection.connect = async function (options) {
let cxn; let cxn;
if (options.cluster) { if (options.cluster) {
cxn = new Redis.Cluster(options.cluster, options.options); const rootNodes = options.cluster.map(node => ({ url : `redis://${node.host}:${node.port}` }));
} else if (options.sentinels) { cxn = createCluster({
cxn = new Redis({
sentinels: options.sentinels,
...options.options, ...options.options,
rootNodes: rootNodes,
});
} else if (options.sentinels) {
const sentinelRootNodes = options.sentinels.map(sentinel => ({ host: sentinel.host, port: sentinel.port }));
cxn = createSentinel({
...options.options,
name: 'sentinel-db',
sentinelRootNodes,
}); });
} else if (redis_socket_or_host && String(redis_socket_or_host).indexOf('/') >= 0) { } else if (redis_socket_or_host && String(redis_socket_or_host).indexOf('/') >= 0) {
// If redis.host contains a path name character, use the unix dom sock connection. ie, /tmp/redis.sock // If redis.host contains a path name character, use the unix dom sock connection. ie, /tmp/redis.sock
cxn = new Redis({ cxn = createClient({
...options.options, ...options.options,
path: redis_socket_or_host,
password: options.password, password: options.password,
db: options.database, database: options.database,
socket: {
path: redis_socket_or_host,
reconnectStrategy: 3000,
},
}); });
} else { } else {
// Else, connect over tcp/ip // Else, connect over tcp/ip
cxn = new Redis({ cxn = createClient({
...options.options, ...options.options,
host: redis_socket_or_host, host: redis_socket_or_host,
port: options.port, port: options.port,
password: options.password, password: options.password,
db: options.database, database: options.database,
socket: {
reconnectStrategy: 3000,
},
}); });
} }
@@ -49,9 +61,14 @@ connection.connect = async function (options) {
}); });
cxn.on('ready', () => { cxn.on('ready', () => {
// back-compat with node_redis // back-compat with node_redis
cxn.batch = cxn.pipeline; cxn.batch = cxn.multi;
resolve(cxn); resolve(cxn);
}); });
cxn.connect().then(() => {
winston.info('Connected to Redis successfully');
}).catch((err) => {
winston.error('Error connecting to Redis:', err);
});
if (options.password) { if (options.password) {
cxn.auth(options.password); cxn.auth(options.password);

View File

@@ -25,12 +25,13 @@ module.exports = function (module) {
if (!Object.keys(data).length) { if (!Object.keys(data).length) {
return; return;
} }
const strObj = helpers.objectFieldsToString(data);
if (Array.isArray(key)) { if (Array.isArray(key)) {
const batch = module.client.batch(); const batch = module.client.batch();
key.forEach(k => batch.hmset(k, data)); key.forEach(k => batch.hSet(k, strObj));
await helpers.execBatch(batch); await helpers.execBatch(batch);
} else { } else {
await module.client.hmset(key, data); await module.client.hSet(key, strObj);
} }
cache.del(key); cache.del(key);
@@ -49,10 +50,16 @@ module.exports = function (module) {
const batch = module.client.batch(); const batch = module.client.batch();
data.forEach((item) => { data.forEach((item) => {
if (Object.keys(item[1]).length) { Object.keys(item[1]).forEach((key) => {
batch.hmset(item[0], item[1]); if (item[1][key] === undefined || item[1][key] === null) {
delete item[1][key];
} }
}); });
if (Object.keys(item[1]).length) {
batch.hSet(item[0], helpers.objectFieldsToString(item[1]));
}
});
await helpers.execBatch(batch); await helpers.execBatch(batch);
cache.del(data.map(item => item[0])); cache.del(data.map(item => item[0]));
}; };
@@ -61,12 +68,15 @@ module.exports = function (module) {
if (!field) { if (!field) {
return; return;
} }
if (value === null || value === undefined) {
return;
}
if (Array.isArray(key)) { if (Array.isArray(key)) {
const batch = module.client.batch(); const batch = module.client.batch();
key.forEach(k => batch.hset(k, field, value)); key.forEach(k => batch.hSet(k, field, String(value)));
await helpers.execBatch(batch); await helpers.execBatch(batch);
} else { } else {
await module.client.hset(key, field, value); await module.client.hSet(key, field, String(value));
} }
cache.del(key); cache.del(key);
@@ -92,9 +102,9 @@ module.exports = function (module) {
const cachedData = {}; const cachedData = {};
cache.getUnCachedKeys([key], cachedData); cache.getUnCachedKeys([key], cachedData);
if (cachedData[key]) { if (cachedData[key]) {
return cachedData[key].hasOwnProperty(field) ? cachedData[key][field] : null; return Object.hasOwn(cachedData[key], field) ? cachedData[key][field] : null;
} }
return await module.client.hget(key, String(field)); return await module.client.hGet(key, String(field));
}; };
module.getObjectFields = async function (key, fields) { module.getObjectFields = async function (key, fields) {
@@ -116,10 +126,10 @@ module.exports = function (module) {
let data = []; let data = [];
if (unCachedKeys.length > 1) { if (unCachedKeys.length > 1) {
const batch = module.client.batch(); const batch = module.client.batch();
unCachedKeys.forEach(k => batch.hgetall(k)); unCachedKeys.forEach(k => batch.hGetAll(k));
data = await helpers.execBatch(batch); data = await helpers.execBatch(batch);
} else if (unCachedKeys.length === 1) { } else if (unCachedKeys.length === 1) {
data = [await module.client.hgetall(unCachedKeys[0])]; data = [await module.client.hGetAll(unCachedKeys[0])];
} }
// convert empty objects into null for back-compat with node_redis // convert empty objects into null for back-compat with node_redis
@@ -149,21 +159,21 @@ module.exports = function (module) {
}; };
module.getObjectKeys = async function (key) { module.getObjectKeys = async function (key) {
return await module.client.hkeys(key); return await module.client.hKeys(key);
}; };
module.getObjectValues = async function (key) { module.getObjectValues = async function (key) {
return await module.client.hvals(key); return await module.client.hVals(key);
}; };
module.isObjectField = async function (key, field) { module.isObjectField = async function (key, field) {
const exists = await module.client.hexists(key, field); const exists = await module.client.hExists(key, String(field));
return exists === 1; return exists === 1;
}; };
module.isObjectFields = async function (key, fields) { module.isObjectFields = async function (key, fields) {
const batch = module.client.batch(); const batch = module.client.batch();
fields.forEach(f => batch.hexists(String(key), String(f))); fields.forEach(f => batch.hExists(String(key), String(f)));
const results = await helpers.execBatch(batch); const results = await helpers.execBatch(batch);
return Array.isArray(results) ? helpers.resultsToBool(results) : null; return Array.isArray(results) ? helpers.resultsToBool(results) : null;
}; };
@@ -174,7 +184,7 @@ module.exports = function (module) {
} }
field = field.toString(); field = field.toString();
if (field) { if (field) {
await module.client.hdel(key, field); await module.client.hDel(key, field);
cache.del(key); cache.del(key);
} }
}; };
@@ -189,10 +199,10 @@ module.exports = function (module) {
} }
if (Array.isArray(key)) { if (Array.isArray(key)) {
const batch = module.client.batch(); const batch = module.client.batch();
key.forEach(k => batch.hdel(k, fields)); key.forEach(k => batch.hDel(k, fields));
await helpers.execBatch(batch); await helpers.execBatch(batch);
} else { } else {
await module.client.hdel(key, fields); await module.client.hDel(key, fields);
} }
cache.del(key); cache.del(key);
@@ -214,10 +224,10 @@ module.exports = function (module) {
let result; let result;
if (Array.isArray(key)) { if (Array.isArray(key)) {
const batch = module.client.batch(); const batch = module.client.batch();
key.forEach(k => batch.hincrby(k, field, value)); key.forEach(k => batch.hIncrBy(k, field, value));
result = await helpers.execBatch(batch); result = await helpers.execBatch(batch);
} else { } else {
result = await module.client.hincrby(key, field, value); result = await module.client.hIncrBy(key, field, value);
} }
cache.del(key); cache.del(key);
return Array.isArray(result) ? result.map(value => parseInt(value, 10)) : parseInt(result, 10); return Array.isArray(result) ? result.map(value => parseInt(value, 10)) : parseInt(result, 10);
@@ -231,7 +241,7 @@ module.exports = function (module) {
const batch = module.client.batch(); const batch = module.client.batch();
data.forEach((item) => { data.forEach((item) => {
for (const [field, value] of Object.entries(item[1])) { for (const [field, value] of Object.entries(item[1])) {
batch.hincrby(item[0], field, value); batch.hIncrBy(item[0], field, value);
} }
}); });
await helpers.execBatch(batch); await helpers.execBatch(batch);

View File

@@ -5,13 +5,8 @@ const helpers = module.exports;
helpers.noop = function () {}; helpers.noop = function () {};
helpers.execBatch = async function (batch) { helpers.execBatch = async function (batch) {
const results = await batch.exec(); const results = await batch.execAsPipeline();
return results.map(([err, res]) => { return results;
if (err) {
throw err;
}
return res;
});
}; };
helpers.resultsToBool = function (results) { helpers.resultsToBool = function (results) {
@@ -21,10 +16,29 @@ helpers.resultsToBool = function (results) {
return results; return results;
}; };
helpers.zsetToObjectArray = function (data) { helpers.objectFieldsToString = function (obj) {
const objects = new Array(data.length / 2); const stringified = Object.fromEntries(
for (let i = 0, k = 0; i < objects.length; i += 1, k += 2) { Object.entries(obj).map(([key, value]) => [key, String(value)])
objects[i] = { value: data[k], score: parseFloat(data[k + 1]) }; );
} return stringified;
return objects; };
helpers.normalizeLexRange = function (min, max, reverse) {
let minmin;
let maxmax;
if (reverse) {
minmin = '+';
maxmax = '-';
} else {
minmin = '-';
maxmax = '+';
}
if (min !== minmin && !min.match(/^[[(]/)) {
min = `[${min}`;
}
if (max !== maxmax && !max.match(/^[[(]/)) {
max = `[${max}`;
}
return { lmin: min, lmax: max };
}; };

View File

@@ -1,27 +1,25 @@
'use strict'; 'use strict';
module.exports = function (module) { module.exports = function (module) {
const helpers = require('./helpers');
module.listPrepend = async function (key, value) { module.listPrepend = async function (key, value) {
if (!key) { if (!key) {
return; return;
} }
await module.client.lpush(key, value); await module.client.lPush(key, Array.isArray(value) ? value.map(String) : String(value));
}; };
module.listAppend = async function (key, value) { module.listAppend = async function (key, value) {
if (!key) { if (!key) {
return; return;
} }
await module.client.rpush(key, value); await module.client.rPush(key, Array.isArray(value) ? value.map(String) : String(value));
}; };
module.listRemoveLast = async function (key) { module.listRemoveLast = async function (key) {
if (!key) { if (!key) {
return; return;
} }
return await module.client.rpop(key); return await module.client.rPop(key);
}; };
module.listRemoveAll = async function (key, value) { module.listRemoveAll = async function (key, value) {
@@ -29,11 +27,11 @@ module.exports = function (module) {
return; return;
} }
if (Array.isArray(value)) { if (Array.isArray(value)) {
const batch = module.client.batch(); const batch = module.client.multi();
value.forEach(value => batch.lrem(key, 0, value)); value.forEach(value => batch.lRem(key, 0, value));
await helpers.execBatch(batch); await batch.execAsPipeline();
} else { } else {
await module.client.lrem(key, 0, value); await module.client.lRem(key, 0, value);
} }
}; };
@@ -41,17 +39,17 @@ module.exports = function (module) {
if (!key) { if (!key) {
return; return;
} }
await module.client.ltrim(key, start, stop); await module.client.lTrim(key, start, stop);
}; };
module.getListRange = async function (key, start, stop) { module.getListRange = async function (key, start, stop) {
if (!key) { if (!key) {
return; return;
} }
return await module.client.lrange(key, start, stop); return await module.client.lRange(key, start, stop);
}; };
module.listLength = async function (key) { module.listLength = async function (key) {
return await module.client.llen(key); return await module.client.lLen(key);
}; };
}; };

View File

@@ -4,7 +4,7 @@ module.exports = function (module) {
const helpers = require('./helpers'); const helpers = require('./helpers');
module.flushdb = async function () { module.flushdb = async function () {
await module.client.send_command('flushdb', []); await module.client.sendCommand(['FLUSHDB']);
}; };
module.emptydb = async function () { module.emptydb = async function () {
@@ -32,9 +32,9 @@ module.exports = function (module) {
const seen = Object.create(null); const seen = Object.create(null);
do { do {
/* eslint-disable no-await-in-loop */ /* eslint-disable no-await-in-loop */
const res = await module.client.scan(cursor, 'MATCH', params.match, 'COUNT', 10000); const res = await module.client.scan(cursor, { MATCH: params.match, COUNT: 10000 });
cursor = res[0]; cursor = res.cursor;
const values = res[1].filter((value) => { const values = res.keys.filter((value) => {
const isSeen = !!seen[value]; const isSeen = !!seen[value];
if (!isSeen) { if (!isSeen) {
seen[value] = 1; seen[value] = 1;
@@ -67,7 +67,7 @@ module.exports = function (module) {
if (!keys || !Array.isArray(keys) || !keys.length) { if (!keys || !Array.isArray(keys) || !keys.length) {
return []; return [];
} }
return await module.client.mget(keys); return await module.client.mGet(keys);
}; };
module.set = async function (key, value) { module.set = async function (key, value) {
@@ -96,26 +96,26 @@ module.exports = function (module) {
}; };
module.expire = async function (key, seconds) { module.expire = async function (key, seconds) {
await module.client.expire(key, seconds); await module.client.EXPIRE(key, seconds);
}; };
module.expireAt = async function (key, timestamp) { module.expireAt = async function (key, timestamp) {
await module.client.expireat(key, timestamp); await module.client.EXPIREAT(key, timestamp);
}; };
module.pexpire = async function (key, ms) { module.pexpire = async function (key, ms) {
await module.client.pexpire(key, ms); await module.client.PEXPIRE(key, ms);
}; };
module.pexpireAt = async function (key, timestamp) { module.pexpireAt = async function (key, timestamp) {
await module.client.pexpireat(key, timestamp); await module.client.PEXPIREAT(key, timestamp);
}; };
module.ttl = async function (key) { module.ttl = async function (key) {
return await module.client.ttl(key); return await module.client.TTL(key);
}; };
module.pttl = async function (key) { module.pttl = async function (key) {
return await module.client.pttl(key); return await module.client.PTTL(key);
}; };
}; };

View File

@@ -10,7 +10,7 @@ module.exports = function (module) {
if (!value.length) { if (!value.length) {
return; return;
} }
await module.client.sadd(key, value); await module.client.sAdd(key, value.map(String));
}; };
module.setsAdd = async function (keys, value) { module.setsAdd = async function (keys, value) {
@@ -18,7 +18,7 @@ module.exports = function (module) {
return; return;
} }
const batch = module.client.batch(); const batch = module.client.batch();
keys.forEach(k => batch.sadd(String(k), String(value))); keys.forEach(k => batch.sAdd(String(k), String(value)));
await helpers.execBatch(batch); await helpers.execBatch(batch);
}; };
@@ -34,57 +34,57 @@ module.exports = function (module) {
} }
const batch = module.client.batch(); const batch = module.client.batch();
key.forEach(k => batch.srem(String(k), value)); key.forEach(k => batch.sRem(String(k), value.map(String)));
await helpers.execBatch(batch); await helpers.execBatch(batch);
}; };
module.setsRemove = async function (keys, value) { module.setsRemove = async function (keys, value) {
const batch = module.client.batch(); const batch = module.client.batch();
keys.forEach(k => batch.srem(String(k), value)); keys.forEach(k => batch.sRem(String(k), String(value)));
await helpers.execBatch(batch); await helpers.execBatch(batch);
}; };
module.isSetMember = async function (key, value) { module.isSetMember = async function (key, value) {
const result = await module.client.sismember(key, value); const result = await module.client.sIsMember(key, String(value));
return result === 1; return result === 1;
}; };
module.isSetMembers = async function (key, values) { module.isSetMembers = async function (key, values) {
const batch = module.client.batch(); const batch = module.client.batch();
values.forEach(v => batch.sismember(String(key), String(v))); values.forEach(v => batch.sIsMember(String(key), String(v)));
const results = await helpers.execBatch(batch); const results = await helpers.execBatch(batch);
return results ? helpers.resultsToBool(results) : null; return results ? helpers.resultsToBool(results) : null;
}; };
module.isMemberOfSets = async function (sets, value) { module.isMemberOfSets = async function (sets, value) {
const batch = module.client.batch(); const batch = module.client.batch();
sets.forEach(s => batch.sismember(String(s), String(value))); sets.forEach(s => batch.sIsMember(String(s), String(value)));
const results = await helpers.execBatch(batch); const results = await helpers.execBatch(batch);
return results ? helpers.resultsToBool(results) : null; return results ? helpers.resultsToBool(results) : null;
}; };
module.getSetMembers = async function (key) { module.getSetMembers = async function (key) {
return await module.client.smembers(key); return await module.client.sMembers(key);
}; };
module.getSetsMembers = async function (keys) { module.getSetsMembers = async function (keys) {
const batch = module.client.batch(); const batch = module.client.batch();
keys.forEach(k => batch.smembers(String(k))); keys.forEach(k => batch.sMembers(String(k)));
return await helpers.execBatch(batch); return await helpers.execBatch(batch);
}; };
module.setCount = async function (key) { module.setCount = async function (key) {
return await module.client.scard(key); return await module.client.sCard(key);
}; };
module.setsCount = async function (keys) { module.setsCount = async function (keys) {
const batch = module.client.batch(); const batch = module.client.batch();
keys.forEach(k => batch.scard(String(k))); keys.forEach(k => batch.sCard(String(k)));
return await helpers.execBatch(batch); return await helpers.execBatch(batch);
}; };
module.setRemoveRandom = async function (key) { module.setRemoveRandom = async function (key) {
return await module.client.spop(key); return await module.client.sPop(key);
}; };
return module; return module;

View File

@@ -11,34 +11,74 @@ module.exports = function (module) {
require('./sorted/intersect')(module); require('./sorted/intersect')(module);
module.getSortedSetRange = async function (key, start, stop) { module.getSortedSetRange = async function (key, start, stop) {
return await sortedSetRange('zrange', key, start, stop, '-inf', '+inf', false); return await sortedSetRange(key, start, stop, '-inf', '+inf', false, false, false);
}; };
module.getSortedSetRevRange = async function (key, start, stop) { module.getSortedSetRevRange = async function (key, start, stop) {
return await sortedSetRange('zrevrange', key, start, stop, '-inf', '+inf', false); return await sortedSetRange(key, start, stop, '-inf', '+inf', false, true, false);
}; };
module.getSortedSetRangeWithScores = async function (key, start, stop) { module.getSortedSetRangeWithScores = async function (key, start, stop) {
return await sortedSetRange('zrange', key, start, stop, '-inf', '+inf', true); return await sortedSetRange(key, start, stop, '-inf', '+inf', true, false, false);
}; };
module.getSortedSetRevRangeWithScores = async function (key, start, stop) { module.getSortedSetRevRangeWithScores = async function (key, start, stop) {
return await sortedSetRange('zrevrange', key, start, stop, '-inf', '+inf', true); return await sortedSetRange(key, start, stop, '-inf', '+inf', true, true, false);
}; };
async function sortedSetRange(method, key, start, stop, min, max, withScores) { module.getSortedSetRangeByScore = async function (key, start, count, min, max) {
return await sortedSetRangeByScore(key, start, count, min, max, false, false);
};
module.getSortedSetRevRangeByScore = async function (key, start, count, max, min) {
return await sortedSetRangeByScore(key, start, count, max, min, false, true);
};
module.getSortedSetRangeByScoreWithScores = async function (key, start, count, min, max) {
return await sortedSetRangeByScore(key, start, count, min, max, true, false);
};
module.getSortedSetRevRangeByScoreWithScores = async function (key, start, count, max, min) {
return await sortedSetRangeByScore(key, start, count, max, min, true, true);
};
async function sortedSetRangeByScore(key, start, count, min, max, withScores, rev) {
if (parseInt(count, 10) === 0) {
return [];
}
const stop = (parseInt(count, 10) === -1) ? -1 : (start + count - 1);
return await sortedSetRange(key, start, stop, min, max, withScores, rev, true);
}
async function sortedSetRange(key, start, stop, min, max, withScores, rev, byScore) {
const opts = {};
const cmd = withScores ? 'zRangeWithScores' : 'zRange';
if (byScore) {
opts.BY = 'SCORE';
opts.LIMIT = { offset: start, count: stop !== -1 ? stop + 1 : stop };
}
if (rev) {
opts.REV = true;
}
if (Array.isArray(key)) { if (Array.isArray(key)) {
if (!key.length) { if (!key.length) {
return []; return [];
} }
const batch = module.client.batch(); const batch = module.client.batch();
key.forEach(key => batch[method](genParams(method, key, 0, stop, min, max, true)));
if (byScore) {
key.forEach(key => batch.zRangeWithScores(key, min, max, {
...opts,
LIMIT: { offset: 0, count: stop !== -1 ? stop + 1 : stop },
}));
} else {
key.forEach(key => batch.zRangeWithScores(key, 0, stop, { ...opts }));
}
const data = await helpers.execBatch(batch); const data = await helpers.execBatch(batch);
const batchData = data;
const batchData = data.map(setData => helpers.zsetToObjectArray(setData)); let objects = dbHelpers.mergeBatch(batchData, 0, stop, rev ? -1 : 1);
let objects = dbHelpers.mergeBatch(batchData, 0, stop, method === 'zrange' ? 1 : -1);
if (start > 0) { if (start > 0) {
objects = objects.slice(start, stop !== -1 ? stop + 1 : undefined); objects = objects.slice(start, stop !== -1 ? stop + 1 : undefined);
} }
@@ -48,63 +88,25 @@ module.exports = function (module) {
return objects; return objects;
} }
const params = genParams(method, key, start, stop, min, max, withScores); let data;
const data = await module.client[method](params); if (byScore) {
data = await module.client[cmd](key, min, max, opts);
} else {
data = await module.client[cmd](key, start, stop, opts);
}
if (!withScores) { if (!withScores) {
return data; return data;
} }
const objects = helpers.zsetToObjectArray(data); return data;
return objects;
}
function genParams(method, key, start, stop, min, max, withScores) {
const params = {
zrevrange: [key, start, stop],
zrange: [key, start, stop],
zrangebyscore: [key, min, max],
zrevrangebyscore: [key, max, min],
};
if (withScores) {
params[method].push('WITHSCORES');
}
if (method === 'zrangebyscore' || method === 'zrevrangebyscore') {
const count = stop !== -1 ? stop - start + 1 : stop;
params[method].push('LIMIT', start, count);
}
return params[method];
}
module.getSortedSetRangeByScore = async function (key, start, count, min, max) {
return await sortedSetRangeByScore('zrangebyscore', key, start, count, min, max, false);
};
module.getSortedSetRevRangeByScore = async function (key, start, count, max, min) {
return await sortedSetRangeByScore('zrevrangebyscore', key, start, count, min, max, false);
};
module.getSortedSetRangeByScoreWithScores = async function (key, start, count, min, max) {
return await sortedSetRangeByScore('zrangebyscore', key, start, count, min, max, true);
};
module.getSortedSetRevRangeByScoreWithScores = async function (key, start, count, max, min) {
return await sortedSetRangeByScore('zrevrangebyscore', key, start, count, min, max, true);
};
async function sortedSetRangeByScore(method, key, start, count, min, max, withScores) {
if (parseInt(count, 10) === 0) {
return [];
}
const stop = (parseInt(count, 10) === -1) ? -1 : (start + count - 1);
return await sortedSetRange(method, key, start, stop, min, max, withScores);
} }
module.sortedSetCount = async function (key, min, max) { module.sortedSetCount = async function (key, min, max) {
return await module.client.zcount(key, min, max); return await module.client.zCount(key, min, max);
}; };
module.sortedSetCard = async function (key) { module.sortedSetCard = async function (key) {
return await module.client.zcard(key); return await module.client.zCard(key);
}; };
module.sortedSetsCard = async function (keys) { module.sortedSetsCard = async function (keys) {
@@ -112,7 +114,7 @@ module.exports = function (module) {
return []; return [];
} }
const batch = module.client.batch(); const batch = module.client.batch();
keys.forEach(k => batch.zcard(String(k))); keys.forEach(k => batch.zCard(String(k)));
return await helpers.execBatch(batch); return await helpers.execBatch(batch);
}; };
@@ -125,26 +127,26 @@ module.exports = function (module) {
} }
const batch = module.client.batch(); const batch = module.client.batch();
if (min !== '-inf' || max !== '+inf') { if (min !== '-inf' || max !== '+inf') {
keys.forEach(k => batch.zcount(String(k), min, max)); keys.forEach(k => batch.zCount(String(k), min, max));
} else { } else {
keys.forEach(k => batch.zcard(String(k))); keys.forEach(k => batch.zCard(String(k)));
} }
const counts = await helpers.execBatch(batch); const counts = await helpers.execBatch(batch);
return counts.reduce((acc, val) => acc + val, 0); return counts.reduce((acc, val) => acc + val, 0);
}; };
module.sortedSetRank = async function (key, value) { module.sortedSetRank = async function (key, value) {
return await module.client.zrank(key, value); return await module.client.zRank(key, String(value));
}; };
module.sortedSetRevRank = async function (key, value) { module.sortedSetRevRank = async function (key, value) {
return await module.client.zrevrank(key, value); return await module.client.zRevRank(key, String(value));
}; };
module.sortedSetsRanks = async function (keys, values) { module.sortedSetsRanks = async function (keys, values) {
const batch = module.client.batch(); const batch = module.client.batch();
for (let i = 0; i < values.length; i += 1) { for (let i = 0; i < values.length; i += 1) {
batch.zrank(keys[i], String(values[i])); batch.zRank(keys[i], String(values[i]));
} }
return await helpers.execBatch(batch); return await helpers.execBatch(batch);
}; };
@@ -152,7 +154,7 @@ module.exports = function (module) {
module.sortedSetsRevRanks = async function (keys, values) { module.sortedSetsRevRanks = async function (keys, values) {
const batch = module.client.batch(); const batch = module.client.batch();
for (let i = 0; i < values.length; i += 1) { for (let i = 0; i < values.length; i += 1) {
batch.zrevrank(keys[i], String(values[i])); batch.zRevRank(keys[i], String(values[i]));
} }
return await helpers.execBatch(batch); return await helpers.execBatch(batch);
}; };
@@ -160,7 +162,7 @@ module.exports = function (module) {
module.sortedSetRanks = async function (key, values) { module.sortedSetRanks = async function (key, values) {
const batch = module.client.batch(); const batch = module.client.batch();
for (let i = 0; i < values.length; i += 1) { for (let i = 0; i < values.length; i += 1) {
batch.zrank(key, String(values[i])); batch.zRank(key, String(values[i]));
} }
return await helpers.execBatch(batch); return await helpers.execBatch(batch);
}; };
@@ -168,7 +170,7 @@ module.exports = function (module) {
module.sortedSetRevRanks = async function (key, values) { module.sortedSetRevRanks = async function (key, values) {
const batch = module.client.batch(); const batch = module.client.batch();
for (let i = 0; i < values.length; i += 1) { for (let i = 0; i < values.length; i += 1) {
batch.zrevrank(key, String(values[i])); batch.zRevRank(key, String(values[i]));
} }
return await helpers.execBatch(batch); return await helpers.execBatch(batch);
}; };
@@ -177,8 +179,7 @@ module.exports = function (module) {
if (!key || value === undefined) { if (!key || value === undefined) {
return null; return null;
} }
const score = await module.client.zScore(key, String(value));
const score = await module.client.zscore(key, value);
return score === null ? score : parseFloat(score); return score === null ? score : parseFloat(score);
}; };
@@ -187,7 +188,7 @@ module.exports = function (module) {
return []; return [];
} }
const batch = module.client.batch(); const batch = module.client.batch();
keys.forEach(key => batch.zscore(String(key), String(value))); keys.forEach(key => batch.zScore(String(key), String(value)));
const scores = await helpers.execBatch(batch); const scores = await helpers.execBatch(batch);
return scores.map(d => (d === null ? d : parseFloat(d))); return scores.map(d => (d === null ? d : parseFloat(d)));
}; };
@@ -197,7 +198,7 @@ module.exports = function (module) {
return []; return [];
} }
const batch = module.client.batch(); const batch = module.client.batch();
values.forEach(value => batch.zscore(String(key), String(value))); values.forEach(value => batch.zScore(String(key), String(value)));
const scores = await helpers.execBatch(batch); const scores = await helpers.execBatch(batch);
return scores.map(d => (d === null ? d : parseFloat(d))); return scores.map(d => (d === null ? d : parseFloat(d)));
}; };
@@ -211,9 +212,9 @@ module.exports = function (module) {
if (!values.length) { if (!values.length) {
return []; return [];
} }
const batch = module.client.batch(); const batch = module.client.multi();
values.forEach(v => batch.zscore(key, String(v))); values.forEach(v => batch.zScore(key, String(v)));
const results = await helpers.execBatch(batch); const results = await batch.execAsPipeline();
return results.map(utils.isNumber); return results.map(utils.isNumber);
}; };
@@ -221,20 +222,18 @@ module.exports = function (module) {
if (!Array.isArray(keys) || !keys.length) { if (!Array.isArray(keys) || !keys.length) {
return []; return [];
} }
const batch = module.client.batch(); const batch = module.client.multi();
keys.forEach(k => batch.zscore(k, String(value))); keys.forEach(k => batch.zScore(k, String(value)));
const results = await helpers.execBatch(batch); const results = await batch.execAsPipeline();
return results.map(utils.isNumber); return results.map(utils.isNumber);
}; };
module.getSortedSetMembers = async function (key) { module.getSortedSetMembers = async function (key) {
return await module.client.zrange(key, 0, -1); return await module.client.zRange(key, 0, -1);
}; };
module.getSortedSetMembersWithScores = async function (key) { module.getSortedSetMembersWithScores = async function (key) {
return helpers.zsetToObjectArray( return await module.client.zRangeWithScores(key, 0, -1);
await module.client.zrange(key, 0, -1, 'WITHSCORES')
);
}; };
module.getSortedSetsMembers = async function (keys) { module.getSortedSetsMembers = async function (keys) {
@@ -242,7 +241,7 @@ module.exports = function (module) {
return []; return [];
} }
const batch = module.client.batch(); const batch = module.client.batch();
keys.forEach(k => batch.zrange(k, 0, -1)); keys.forEach(k => batch.zRange(k, 0, -1));
return await helpers.execBatch(batch); return await helpers.execBatch(batch);
}; };
@@ -251,65 +250,52 @@ module.exports = function (module) {
return []; return [];
} }
const batch = module.client.batch(); const batch = module.client.batch();
keys.forEach(k => batch.zrange(k, 0, -1, 'WITHSCORES')); keys.forEach(k => batch.zRangeWithScores(k, 0, -1));
const res = await helpers.execBatch(batch); const res = await helpers.execBatch(batch);
return res.map(helpers.zsetToObjectArray); return res;
}; };
module.sortedSetIncrBy = async function (key, increment, value) { module.sortedSetIncrBy = async function (key, increment, value) {
const newValue = await module.client.zincrby(key, increment, value); const newValue = await module.client.zIncrBy(key, increment, String(value));
return parseFloat(newValue); return parseFloat(newValue);
}; };
module.sortedSetIncrByBulk = async function (data) { module.sortedSetIncrByBulk = async function (data) {
const multi = module.client.multi(); const multi = module.client.multi();
data.forEach((item) => { data.forEach((item) => {
multi.zincrby(item[0], item[1], item[2]); multi.zIncrBy(item[0], item[1], String(item[2]));
}); });
const result = await multi.exec(); const result = await multi.exec();
return result.map(item => item && parseFloat(item[1])); return result;
}; };
module.getSortedSetRangeByLex = async function (key, min, max, start, count) { module.getSortedSetRangeByLex = async function (key, min, max, start = 0, count = -1) {
return await sortedSetLex('zrangebylex', false, key, min, max, start, count); const { lmin, lmax } = helpers.normalizeLexRange(min, max, false);
return await module.client.zRange(key, lmin, lmax, {
BY: 'LEX',
LIMIT: { offset: start, count: count },
});
}; };
module.getSortedSetRevRangeByLex = async function (key, max, min, start, count) { module.getSortedSetRevRangeByLex = async function (key, max, min, start = 0, count = -1) {
return await sortedSetLex('zrevrangebylex', true, key, max, min, start, count); const { lmin, lmax } = helpers.normalizeLexRange(max, min, true);
return await module.client.zRange(key, lmin, lmax, {
REV: true,
BY: 'LEX',
LIMIT: { offset: start, count: count },
});
}; };
module.sortedSetRemoveRangeByLex = async function (key, min, max) { module.sortedSetRemoveRangeByLex = async function (key, min, max) {
await sortedSetLex('zremrangebylex', false, key, min, max); const { lmin, lmax } = helpers.normalizeLexRange(min, max, false);
await module.client.zRemRangeByLex(key, lmin, lmax);
}; };
module.sortedSetLexCount = async function (key, min, max) { module.sortedSetLexCount = async function (key, min, max) {
return await sortedSetLex('zlexcount', false, key, min, max); const { lmin, lmax } = helpers.normalizeLexRange(min, max, false);
return await module.client.zLexCount(key, lmin, lmax);
}; };
async function sortedSetLex(method, reverse, key, min, max, start, count) {
let minmin;
let maxmax;
if (reverse) {
minmin = '+';
maxmax = '-';
} else {
minmin = '-';
maxmax = '+';
}
if (min !== minmin && !min.match(/^[[(]/)) {
min = `[${min}`;
}
if (max !== maxmax && !max.match(/^[[(]/)) {
max = `[${max}`;
}
const args = [key, min, max];
if (count) {
args.push('LIMIT', start, count);
}
return await module.client[method](args);
}
module.getSortedSetScan = async function (params) { module.getSortedSetScan = async function (params) {
let cursor = '0'; let cursor = '0';
@@ -318,20 +304,19 @@ module.exports = function (module) {
const seen = Object.create(null); const seen = Object.create(null);
do { do {
/* eslint-disable no-await-in-loop */ /* eslint-disable no-await-in-loop */
const res = await module.client.zscan(params.key, cursor, 'MATCH', params.match, 'COUNT', 5000); const res = await module.client.zScan(params.key, cursor, { MATCH: params.match, COUNT: 5000 });
cursor = res[0]; cursor = res.cursor;
done = cursor === '0'; done = cursor === '0';
const data = res[1];
for (let i = 0; i < data.length; i += 2) { for (let i = 0; i < res.members.length; i ++) {
const value = data[i]; const item = res.members[i];
if (!seen[value]) { if (!seen[item.value]) {
seen[value] = 1; seen[item.value] = 1;
if (params.withScores) { if (params.withScores) {
returnData.push({ value: value, score: parseFloat(data[i + 1]) }); returnData.push({ value: item.value, score: parseFloat(item.score) });
} else { } else {
returnData.push(value); returnData.push(item.value);
} }
if (params.limit && returnData.length >= params.limit) { if (params.limit && returnData.length >= params.limit) {
done = true; done = true;

View File

@@ -1,7 +1,6 @@
'use strict'; 'use strict';
module.exports = function (module) { module.exports = function (module) {
const helpers = require('../helpers');
const utils = require('../../../utils'); const utils = require('../../../utils');
module.sortedSetAdd = async function (key, score, value) { module.sortedSetAdd = async function (key, score, value) {
@@ -14,7 +13,8 @@ module.exports = function (module) {
if (!utils.isNumber(score)) { if (!utils.isNumber(score)) {
throw new Error(`[[error:invalid-score, ${score}]]`); throw new Error(`[[error:invalid-score, ${score}]]`);
} }
await module.client.zadd(key, score, String(value));
await module.client.zAdd(key, { score, value: String(value) });
}; };
async function sortedSetAddMulti(key, scores, values) { async function sortedSetAddMulti(key, scores, values) {
@@ -30,11 +30,8 @@ module.exports = function (module) {
throw new Error(`[[error:invalid-score, ${scores[i]}]]`); throw new Error(`[[error:invalid-score, ${scores[i]}]]`);
} }
} }
const args = [key]; const members = scores.map((score, i) => ({ score, value: String(values[i])}));
for (let i = 0; i < scores.length; i += 1) { await module.client.zAdd(key, members);
args.push(scores[i], String(values[i]));
}
await module.client.zadd(args);
} }
module.sortedSetsAdd = async function (keys, scores, value) { module.sortedSetsAdd = async function (keys, scores, value) {
@@ -51,13 +48,16 @@ module.exports = function (module) {
throw new Error('[[error:invalid-data]]'); throw new Error('[[error:invalid-data]]');
} }
const batch = module.client.batch(); const batch = module.client.multi();
for (let i = 0; i < keys.length; i += 1) { for (let i = 0; i < keys.length; i += 1) {
if (keys[i]) { if (keys[i]) {
batch.zadd(keys[i], isArrayOfScores ? scores[i] : scores, String(value)); batch.zAdd(keys[i], {
score: isArrayOfScores ? scores[i] : scores,
value: String(value),
});
} }
} }
await helpers.execBatch(batch); await batch.execAsPipeline();
}; };
module.sortedSetAddBulk = async function (data) { module.sortedSetAddBulk = async function (data) {
@@ -69,8 +69,8 @@ module.exports = function (module) {
if (!utils.isNumber(item[1])) { if (!utils.isNumber(item[1])) {
throw new Error(`[[error:invalid-score, ${item[1]}]]`); throw new Error(`[[error:invalid-score, ${item[1]}]]`);
} }
batch.zadd(item[0], item[1], item[2]); batch.zAdd(item[0], { score: item[1], value: String(item[2]) });
}); });
await helpers.execBatch(batch); await batch.execAsPipeline();
}; };
}; };

View File

@@ -8,52 +8,46 @@ module.exports = function (module) {
return 0; return 0;
} }
const tempSetName = `temp_${Date.now()}`; const tempSetName = `temp_${Date.now()}`;
const interParams = [tempSetName, keys.length].concat(keys);
const multi = module.client.multi(); const multi = module.client.multi();
multi.zinterstore(interParams); multi.zInterStore(tempSetName, keys);
multi.zcard(tempSetName); multi.zCard(tempSetName);
multi.del(tempSetName); multi.del(tempSetName);
const results = await helpers.execBatch(multi); const results = await helpers.execBatch(multi);
return results[1] || 0; return results[1] || 0;
}; };
module.getSortedSetIntersect = async function (params) { module.getSortedSetIntersect = async function (params) {
params.method = 'zrange'; params.reverse = false;
return await getSortedSetRevIntersect(params); return await getSortedSetRevIntersect(params);
}; };
module.getSortedSetRevIntersect = async function (params) { module.getSortedSetRevIntersect = async function (params) {
params.method = 'zrevrange'; params.reverse = true;
return await getSortedSetRevIntersect(params); return await getSortedSetRevIntersect(params);
}; };
async function getSortedSetRevIntersect(params) { async function getSortedSetRevIntersect(params) {
const { sets } = params; let { sets } = params;
const start = params.hasOwnProperty('start') ? params.start : 0; const start = params.hasOwnProperty('start') ? params.start : 0;
const stop = params.hasOwnProperty('stop') ? params.stop : -1; const stop = params.hasOwnProperty('stop') ? params.stop : -1;
const weights = params.weights || []; const weights = params.weights || [];
const tempSetName = `temp_${Date.now()}`; const tempSetName = `temp_${Date.now()}`;
let interParams = [tempSetName, sets.length].concat(sets); const interParams = {};
if (weights.length) { if (weights.length) {
interParams = interParams.concat(['WEIGHTS'].concat(weights)); sets = sets.map((set, index) => ({ key: set, weight: weights[index] }));
} }
if (params.aggregate) { if (params.aggregate) {
interParams = interParams.concat(['AGGREGATE', params.aggregate]); interParams['AGGREGATE'] = params.aggregate.toUpperCase();
} }
const rangeParams = [tempSetName, start, stop]; const rangeCmd = params.withScores ? 'zRangeWithScores' : 'zRange';
if (params.withScores) {
rangeParams.push('WITHSCORES');
}
const multi = module.client.multi(); const multi = module.client.multi();
multi.zinterstore(interParams); multi.zInterStore(tempSetName, sets, interParams);
multi[params.method](rangeParams); multi[rangeCmd](tempSetName, start, stop, { REV: params.reverse});
multi.del(tempSetName); multi.del(tempSetName);
let results = await helpers.execBatch(multi); let results = await helpers.execBatch(multi);
@@ -61,6 +55,6 @@ module.exports = function (module) {
return results ? results[1] : null; return results ? results[1] : null;
} }
results = results[1] || []; results = results[1] || [];
return helpers.zsetToObjectArray(results); return results;
} }
}; };

View File

@@ -18,10 +18,10 @@ module.exports = function (module) {
if (Array.isArray(key)) { if (Array.isArray(key)) {
const batch = module.client.batch(); const batch = module.client.batch();
key.forEach(k => batch.zrem(k, value)); key.forEach(k => batch.zRem(k, value.map(String)));
await helpers.execBatch(batch); await helpers.execBatch(batch);
} else { } else {
await module.client.zrem(key, value); await module.client.zRem(key, value.map(String));
} }
}; };
@@ -31,7 +31,7 @@ module.exports = function (module) {
module.sortedSetsRemoveRangeByScore = async function (keys, min, max) { module.sortedSetsRemoveRangeByScore = async function (keys, min, max) {
const batch = module.client.batch(); const batch = module.client.batch();
keys.forEach(k => batch.zremrangebyscore(k, min, max)); keys.forEach(k => batch.zRemRangeByScore(k, min, max));
await helpers.execBatch(batch); await helpers.execBatch(batch);
}; };
@@ -40,7 +40,7 @@ module.exports = function (module) {
return; return;
} }
const batch = module.client.batch(); const batch = module.client.batch();
data.forEach(item => batch.zrem(item[0], item[1])); data.forEach(item => batch.zRem(item[0], String(item[1])));
await helpers.execBatch(batch); await helpers.execBatch(batch);
}; };
}; };

View File

@@ -4,25 +4,20 @@
module.exports = function (module) { module.exports = function (module) {
const helpers = require('../helpers'); const helpers = require('../helpers');
module.sortedSetUnionCard = async function (keys) { module.sortedSetUnionCard = async function (keys) {
const tempSetName = `temp_${Date.now()}`;
if (!keys.length) { if (!keys.length) {
return 0; return 0;
} }
const multi = module.client.multi(); const results = await module.client.zUnion(keys);
multi.zunionstore([tempSetName, keys.length].concat(keys)); return results ? results.length : 0;
multi.zcard(tempSetName);
multi.del(tempSetName);
const results = await helpers.execBatch(multi);
return Array.isArray(results) && results.length ? results[1] : 0;
}; };
module.getSortedSetUnion = async function (params) { module.getSortedSetUnion = async function (params) {
params.method = 'zrange'; params.reverse = false;
return await module.sortedSetUnion(params); return await module.sortedSetUnion(params);
}; };
module.getSortedSetRevUnion = async function (params) { module.getSortedSetRevUnion = async function (params) {
params.method = 'zrevrange'; params.reverse = true;
return await module.sortedSetUnion(params); return await module.sortedSetUnion(params);
}; };
@@ -32,21 +27,16 @@ module.exports = function (module) {
} }
const tempSetName = `temp_${Date.now()}`; const tempSetName = `temp_${Date.now()}`;
const rangeCmd = params.withScores ? 'zRangeWithScores' : 'zRange';
const rangeParams = [tempSetName, params.start, params.stop];
if (params.withScores) {
rangeParams.push('WITHSCORES');
}
const multi = module.client.multi(); const multi = module.client.multi();
multi.zunionstore([tempSetName, params.sets.length].concat(params.sets)); multi.zUnionStore(tempSetName, params.sets);
multi[params.method](rangeParams); multi[rangeCmd](tempSetName, params.start, params.stop, { REV: params.reverse });
multi.del(tempSetName); multi.del(tempSetName);
let results = await helpers.execBatch(multi); let results = await helpers.execBatch(multi);
if (!params.withScores) { if (!params.withScores) {
return results ? results[1] : null; return results ? results[1] : null;
} }
results = results[1] || []; results = results[1] || [];
return helpers.zsetToObjectArray(results); return results;
}; };
}; };

View File

@@ -442,7 +442,7 @@ module.exports = function (Topics) {
let { content } = postData; let { content } = postData;
// ignore lines that start with `>` // ignore lines that start with `>`
content = content.split('\n').filter(line => !line.trim().startsWith('>')).join('\n'); content = (content || '').split('\n').filter(line => !line.trim().startsWith('>')).join('\n');
// Scan post content for topic links // Scan post content for topic links
const matches = [...content.matchAll(backlinkRegex)]; const matches = [...content.matchAll(backlinkRegex)];
if (!matches) { if (!matches) {

View File

@@ -449,7 +449,8 @@ describe('Inbox resolution', () => {
await activitypub.actors.assert(id); await activitypub.actors.assert(id);
const inboxes = await activitypub.resolveInboxes([id]); const inboxes = await activitypub.resolveInboxes([id]);
console.log('inboxes', inboxes);
console.log('actor', actor);
assert(inboxes && Array.isArray(inboxes)); assert(inboxes && Array.isArray(inboxes));
assert.strictEqual(inboxes.length, 1); assert.strictEqual(inboxes.length, 1);
assert.strictEqual(inboxes[0], actor.inbox); assert.strictEqual(inboxes[0], actor.inbox);

View File

@@ -650,7 +650,7 @@ describe('Notes', () => {
it('should upvote an asserted remote post', async () => { it('should upvote an asserted remote post', async () => {
const { id } = helpers.mocks.note(); const { id } = helpers.mocks.note();
await activitypub.notes.assert(0, [id], { skipChecks: true }); await activitypub.notes.assert(0, id, { skipChecks: true });
const { activity: like } = helpers.mocks.like({ const { activity: like } = helpers.mocks.like({
object: id, object: id,
}); });
@@ -672,7 +672,7 @@ describe('Notes', () => {
it('should update a note\'s content', async () => { it('should update a note\'s content', async () => {
const { id: actor } = helpers.mocks.person(); const { id: actor } = helpers.mocks.person();
const { id, note } = helpers.mocks.note({ attributedTo: actor }); const { id, note } = helpers.mocks.note({ attributedTo: actor });
await activitypub.notes.assert(0, [id], { skipChecks: true }); await activitypub.notes.assert(0, id, { skipChecks: true });
note.content = utils.generateUUID(); note.content = utils.generateUUID();
const { activity: update } = helpers.mocks.update({ object: note }); const { activity: update } = helpers.mocks.update({ object: note });
const { activity } = helpers.mocks.announce({ object: update }); const { activity } = helpers.mocks.announce({ object: update });

View File

@@ -501,7 +501,9 @@ NUMERIC)-- WsPn&query[cid]=-1&parentCid=0&selectedCids[]=-1&privilege=topics:rea
['byScoreWithScoresKeys1', 1, 'value1'], ['byScoreWithScoresKeys1', 1, 'value1'],
['byScoreWithScoresKeys2', 2, 'value2'], ['byScoreWithScoresKeys2', 2, 'value2'],
]); ]);
const data = await db.getSortedSetRevRangeByScoreWithScores(['byScoreWithScoresKeys1', 'byScoreWithScoresKeys2'], 0, -1, 5, -5); const data = await db.getSortedSetRevRangeByScoreWithScores([
'byScoreWithScoresKeys1', 'byScoreWithScoresKeys2',
], 0, -1, 5, -5);
assert.deepStrictEqual(data, [{ value: 'value2', score: 2 }, { value: 'value1', score: 1 }]); assert.deepStrictEqual(data, [{ value: 'value2', score: 2 }, { value: 'value1', score: 1 }]);
}); });
}); });
@@ -1144,23 +1146,17 @@ NUMERIC)-- WsPn&query[cid]=-1&parentCid=0&selectedCids[]=-1&privilege=topics:rea
assert.strictEqual(await db.exists('sorted3'), false); assert.strictEqual(await db.exists('sorted3'), false);
}); });
it('should remove multiple values from multiple keys', (done) => { it('should remove multiple values from multiple keys', async () => {
db.sortedSetAdd('multiTest1', [1, 2, 3, 4], ['one', 'two', 'three', 'four'], (err) => { await db.sortedSetAdd('multiTest1', [1, 2, 3, 4], ['one', 'two', 'three', 'four']);
assert.ifError(err); await db.sortedSetAdd('multiTest2', [3, 4, 5, 6], ['three', 'four', 'five', 'six']);
db.sortedSetAdd('multiTest2', [3, 4, 5, 6], ['three', 'four', 'five', 'six'], (err) => {
assert.ifError(err); await db.sortedSetRemove(['multiTest1', 'multiTest2'], ['two', 'three', 'four', 'five', 'doesnt exist']);
db.sortedSetRemove(['multiTest1', 'multiTest2'], ['two', 'three', 'four', 'five', 'doesnt exist'], (err) => {
assert.ifError(err); const members = await db.getSortedSetsMembers(['multiTest1', 'multiTest2']);
db.getSortedSetsMembers(['multiTest1', 'multiTest2'], (err, members) => {
assert.ifError(err);
assert.equal(members[0].length, 1); assert.equal(members[0].length, 1);
assert.equal(members[1].length, 1); assert.equal(members[1].length, 1);
assert.deepEqual(members, [['one'], ['six']]); assert.deepEqual(members, [['one'], ['six']]);
done();
});
});
});
});
}); });
it('should remove value from multiple keys', async () => { it('should remove value from multiple keys', async () => {
@@ -1171,24 +1167,15 @@ NUMERIC)-- WsPn&query[cid]=-1&parentCid=0&selectedCids[]=-1&privilege=topics:rea
assert.deepStrictEqual(await db.getSortedSetRange('multiTest4', 0, -1), ['four', 'five', 'six']); assert.deepStrictEqual(await db.getSortedSetRange('multiTest4', 0, -1), ['four', 'five', 'six']);
}); });
it('should remove multiple values from multiple keys', (done) => { it('should remove multiple values from multiple keys', async () => {
db.sortedSetAdd('multiTest5', [1], ['one'], (err) => { await db.sortedSetAdd('multiTest5', [1], ['one']);
assert.ifError(err); await db.sortedSetAdd('multiTest6', [2], ['two']);
db.sortedSetAdd('multiTest6', [2], ['two'], (err) => { await db.sortedSetAdd('multiTest7', [3], [333]);
assert.ifError(err);
db.sortedSetAdd('multiTest7', [3], [333], (err) => { await db.sortedSetRemove(['multiTest5', 'multiTest6', 'multiTest7'], ['one', 'two', 333]);
assert.ifError(err);
db.sortedSetRemove(['multiTest5', 'multiTest6', 'multiTest7'], ['one', 'two', 333], (err) => { const members = await db.getSortedSetsMembers(['multiTest5', 'multiTest6', 'multiTest7']);
assert.ifError(err);
db.getSortedSetsMembers(['multiTest5', 'multiTest6', 'multiTest7'], (err, members) => {
assert.ifError(err);
assert.deepEqual(members, [[], [], []]); assert.deepEqual(members, [[], [], []]);
done();
});
});
});
});
});
}); });
it('should not remove anything if values is empty array', (done) => { it('should not remove anything if values is empty array', (done) => {
@@ -1379,7 +1366,10 @@ NUMERIC)-- WsPn&query[cid]=-1&parentCid=0&selectedCids[]=-1&privilege=topics:rea
weights: [1, 0.5], weights: [1, 0.5],
}, (err, data) => { }, (err, data) => {
assert.ifError(err); assert.ifError(err);
assert.deepEqual([{ value: 'value2', score: 4 }, { value: 'value3', score: 5.5 }], data); assert.deepEqual([
{ value: 'value2', score: 4 },
{ value: 'value3', score: 5.5 },
], data);
done(); done();
}); });
}); });

View File

@@ -171,7 +171,6 @@ before(async function () {
require('../../src/user').startJobs(); require('../../src/user').startJobs();
await webserver.listen(); await webserver.listen();
// Iterate over all of the test suites/contexts // Iterate over all of the test suites/contexts
this.test.parent.suites.forEach((suite) => { this.test.parent.suites.forEach((suite) => {
// Attach an afterAll listener that resets the defaults // Attach an afterAll listener that resets the defaults