diff --git a/.gitignore b/.gitignore
index c3046b5..aa4ebe0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,3 +15,4 @@ tsOut/
.vscode/
coverage/
.nyc_output/
+.DS_Store
diff --git a/.travis.yml b/.travis.yml
index e9151d8..b8ccbee 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,14 +1,17 @@
language: node_js
node_js:
- - "stable"
- - "lts/*"
- - "8"
+ - 'stable'
+ - 'lts/*'
+ - '8'
services:
- redis-server
-before_script:
+before_script:
- npm run build
script:
- npm run test
- npm run coverage:failIfLow
-after_success:
+after_success:
- npm run coverage:coveralls
+env:
+ - NOHM_TEST_IOREDIS="false"
+ - NOHM_TEST_IOREDIS="true"
diff --git a/docs/api/NohmClass.html b/docs/api/NohmClass.html
index 7896bd4..6bc8abd 100644
--- a/docs/api/NohmClass.html
+++ b/docs/api/NohmClass.html
@@ -176,7 +176,7 @@
(async) factor
Source:
@@ -428,7 +428,7 @@ getModelsSource:
@@ -981,7 +981,7 @@ Source:
@@ -318,7 +318,7 @@ (async, static)
Source:
@@ -500,7 +500,7 @@ (async, static)
Source:
@@ -651,7 +651,7 @@ (async, static) Source:
@@ -832,7 +832,7 @@ (async, static)
Source:
@@ -985,7 +985,7 @@ (async, static) Source:
@@ -1136,7 +1136,7 @@ (async, static) Source:
diff --git a/docs/api/tsOut_index.js.html b/docs/api/tsOut_index.js.html
index 8486c93..ad2f574 100644
--- a/docs/api/tsOut_index.js.html
+++ b/docs/api/tsOut_index.js.html
@@ -165,11 +165,13 @@ tsOut/index.js
* Note: this will not affect models that have a client set on their own.
*/
setClient(client) {
- debug('Setting new redis client. Connected: %s; Address: %s.', client && client.connected, client && client.address);
- if (client && !client.connected) {
+ debug('Setting new redis client. Connected: %s; Address: %s.', client && (client.connected || client.status === 'ready'), client && client.address);
+ // ioredis uses .status string instead of .connected boolean
+ if (client && !(client.connected || client.status === 'ready')) {
this
.logError(`WARNING: setClient() received a redis client that is not connected yet.
- Consider waiting for an established connection before setting it.`);
+ Consider waiting for an established connection before setting it. Status (if ioredis): ${client.status}
+ , connected (if node_redis): ${client.connected}`);
}
else if (!client) {
client = redis.createClient();
@@ -650,7 +652,7 @@ tsOut/index.js
async purgeDb(client = this.client) {
function delKeys(prefix) {
return new Promise((resolve, reject) => {
- client.KEYS(prefix + '*', (err, keys) => {
+ client.keys(prefix + '*', (err, keys) => {
if (err) {
reject(err);
}
@@ -658,7 +660,7 @@ tsOut/index.js
resolve();
}
else {
- client.DEL(keys, (innerErr) => {
+ client.del(keys, (innerErr) => {
if (innerErr) {
reject(innerErr);
}
@@ -734,7 +736,7 @@ tsOut/index.js
this.publishEventEmitter = new events_1.EventEmitter();
this.publishEventEmitter.setMaxListeners(0); // TODO: check if this is sensible
this.isPublishSubscribed = true;
- await typed_redis_helper_1.PSUBSCRIBE(this.publishClient, this.prefix.channel + PUBSUB_ALL_PATTERN);
+ await typed_redis_helper_1.psubscribe(this.publishClient, this.prefix.channel + PUBSUB_ALL_PATTERN);
debugPubSub(`Redis PSUBSCRIBE for '%s'.`, this.prefix.channel + PUBSUB_ALL_PATTERN);
this.publishClient.on('pmessage', (_pattern, channel, message) => {
const suffix = channel.slice(this.prefix.channel.length);
@@ -782,7 +784,7 @@ tsOut/index.js
if (this.isPublishSubscribed === true) {
debugPubSub(`Redis PUNSUBSCRIBE for '%s'.`, this.prefix.channel + PUBSUB_ALL_PATTERN);
this.isPublishSubscribed = false;
- await typed_redis_helper_1.PUNSUBSCRIBE(this.publishClient, this.prefix.channel + PUBSUB_ALL_PATTERN);
+ await typed_redis_helper_1.punsubscribe(this.publishClient, this.prefix.channel + PUBSUB_ALL_PATTERN);
}
return this.publishClient;
}
diff --git a/docs/api/tsOut_model.js.html b/docs/api/tsOut_model.js.html
index a1955cf..2bfed39 100644
--- a/docs/api/tsOut_model.js.html
+++ b/docs/api/tsOut_model.js.html
@@ -200,14 +200,14 @@ tsOut/model.js
}
});
try {
- const dbVersion = await typed_redis_helper_1.GET(this.client, versionKey);
+ const dbVersion = await typed_redis_helper_1.get(this.client, versionKey);
if (this.meta.version !== dbVersion) {
const generator = this.options.idGenerator || 'default';
debug(`Setting meta for model '%s' with version '%s' and idGenerator '%s' to %j.`, this.modelName, this.meta.version, generator, properties);
await Promise.all([
- typed_redis_helper_1.SET(this.client, idGeneratorKey, generator.toString()),
- typed_redis_helper_1.SET(this.client, propertiesKey, JSON.stringify(properties)),
- typed_redis_helper_1.SET(this.client, versionKey, this.meta.version),
+ typed_redis_helper_1.set(this.client, idGeneratorKey, generator.toString()),
+ typed_redis_helper_1.set(this.client, propertiesKey, JSON.stringify(properties)),
+ typed_redis_helper_1.set(this.client, versionKey, this.meta.version),
]);
}
this.meta.inDb = true;
@@ -480,7 +480,7 @@ tsOut/model.js
}
let numIdExisting = 0;
if (action !== 'create') {
- numIdExisting = await typed_redis_helper_1.SISMEMBER(this.client, this.prefix('idsets'), this.id);
+ numIdExisting = await typed_redis_helper_1.sismember(this.client, this.prefix('idsets'), this.id);
}
if (action === 'create' && numIdExisting === 0) {
debug(`Creating new instance '%s.%s' because action was '%s' and numIdExisting was %d.`, this.modelName, this.id, action, numIdExisting);
@@ -498,7 +498,7 @@ tsOut/model.js
}
async create() {
const id = await this.generateId();
- await typed_redis_helper_1.SADD(this.client, this.prefix('idsets'), id);
+ await typed_redis_helper_1.sadd(this.client, this.prefix('idsets'), id);
await this.setUniqueIds(id);
this.id = id;
}
@@ -542,7 +542,7 @@ tsOut/model.js
}
if (mSetArguments.length !== 0) {
debug(`Setting all unique indices of model '%s.%s' to new id '%s'.`, this.modelName, this.id, id);
- return typed_redis_helper_1.MSET(this.client, mSetArguments);
+ return typed_redis_helper_1.mset(this.client, mSetArguments);
}
}
async update(options) {
@@ -550,7 +550,7 @@ tsOut/model.js
throw new Error('Update was called without having an id set.');
}
const hmSetArguments = [];
- const client = this.client.MULTI();
+ const client = this.client.multi();
const isCreate = !this.inDb;
hmSetArguments.push(`${this.prefix('hash')}:${this.id}`);
for (const [key, prop] of this.properties) {
@@ -560,10 +560,10 @@ tsOut/model.js
}
if (hmSetArguments.length > 1) {
hmSetArguments.push('__meta_version', this.meta.version);
- client.HMSET.apply(client, hmSetArguments);
+ client.hmset.apply(client, hmSetArguments);
}
await this.setIndices(client);
- await typed_redis_helper_1.EXEC(client);
+ await typed_redis_helper_1.exec(client);
const linkResults = await this.storeLinks(options);
this.relationChanges = [];
const linkFailures = linkResults.filter((linkResult) => !linkResult.success);
@@ -654,7 +654,7 @@ tsOut/model.js
const foreignName = `${change.options.name}Foreign`;
const command = change.action === 'link' ? 'sadd' : 'srem';
const relationKeyPrefix = this.rawPrefix().relationKeys;
- const multi = this.client.MULTI();
+ const multi = this.client.multi();
// relation to other
const toKey = this.getRelationKey(change.object.modelName, change.options.name);
// first store the information to which other model names the instance has a relation to
@@ -667,7 +667,7 @@ tsOut/model.js
multi[command](fromKey, this.stringId());
try {
debug(`Linking in redis.`, this.modelName, change.options.name, command);
- await typed_redis_helper_1.EXEC(multi);
+ await typed_redis_helper_1.exec(multi);
if (!change.options.silent) {
this.fireEvent(change.action, change.object, change.options.name);
}
@@ -704,7 +704,7 @@ tsOut/model.js
oldUniqueValue = oldUniqueValue.toLowerCase();
}
debug(`Removing old unique '%s' from '%s.%s' because propUpdated: %o && this.inDb %o.`, key, this.modelName, this.id, prop.__updated, this.inDb);
- multi.DEL(`${this.prefix('unique')}:${key}:${oldUniqueValue}`, this.nohmClass.logError);
+ multi.del(`${this.prefix('unique')}:${key}:${oldUniqueValue}`, this.nohmClass.logError);
}
// set new normal index
if (isIndex && isDirty) {
@@ -712,14 +712,14 @@ tsOut/model.js
debug(`Adding numeric index '%s' to '%s.%s'.`, key, this.modelName, this.id, prop.__updated);
// we use scored sets for things like "get all users older than 5"
const scoredPrefix = this.prefix('scoredindex');
- multi.ZADD(`${scoredPrefix}:${key}`, prop.value, this.stringId(), this.nohmClass.logError);
+ multi.zadd(`${scoredPrefix}:${key}`, prop.value, this.stringId(), this.nohmClass.logError);
}
debug(`Adding index '%s' to '%s.%s'; isInDb: '%s'; newValue: '%s'; oldValue: '%s'.`, key, this.modelName, this.stringId(), isInDb, prop.value, oldValue);
const prefix = this.prefix('index');
if (isInDb) {
- multi.SREM(`${prefix}:${key}:${oldValue}`, this.stringId(), this.nohmClass.logError);
+ multi.srem(`${prefix}:${key}:${oldValue}`, this.stringId(), this.nohmClass.logError);
}
- multi.SADD(`${prefix}:${key}:${prop.value}`, this.stringId(), this.nohmClass.logError);
+ multi.sadd(`${prefix}:${key}:${prop.value}`, this.stringId(), this.nohmClass.logError);
}
}
}
@@ -859,10 +859,10 @@ tsOut/model.js
* We lock the unique value here if it's not locked yet, then later remove the old uniquelock
* when really saving it. (or we free the unique slot if we're not saving)
*/
- dbValue = await typed_redis_helper_1.SETNX(this.client, key, this.stringId());
+ dbValue = await typed_redis_helper_1.setnx(this.client, key, this.stringId());
}
else {
- dbValue = await typed_redis_helper_1.EXISTS(this.client, key);
+ dbValue = await typed_redis_helper_1.exists(this.client, key);
}
let isFreeUnique = false;
if (setDirectly && dbValue === 1) {
@@ -878,7 +878,7 @@ tsOut/model.js
// setDirectly === true means using setnx which returns 1 if the value did *not* exist
// if it did exist, we check if the unique is the same as the one on this model.
// see https://github.com/maritz/nohm/issues/82 for use-case
- const dbId = await typed_redis_helper_1.GET(this.client, key);
+ const dbId = await typed_redis_helper_1.get(this.client, key);
if (dbId === this.stringId()) {
isFreeUnique = true;
}
@@ -926,7 +926,7 @@ tsOut/model.js
if (this.tmpUniqueKeys.length > 0) {
debug(`Clearing temp uniques '%o' for '%s.%s'.`, this.tmpUniqueKeys, this.modelName, this.id);
const deletes = this.tmpUniqueKeys.map((key) => {
- return typed_redis_helper_1.DEL(this.client, key);
+ return typed_redis_helper_1.del(this.client, key);
});
await Promise.all(deletes);
}
@@ -957,7 +957,7 @@ tsOut/model.js
}
async deleteDbCall() {
// TODO: write test for removal of relationKeys - purgeDb kinda tests it already, but not enough
- const multi = this.client.MULTI();
+ const multi = this.client.multi();
multi.del(`${this.prefix('hash')}:${this.stringId()}`);
multi.srem(this.prefix('idsets'), this.stringId());
this.properties.forEach((prop, key) => {
@@ -976,7 +976,7 @@ tsOut/model.js
}
});
await this.unlinkAll(multi);
- await typed_redis_helper_1.EXEC(multi);
+ await typed_redis_helper_1.exec(multi);
}
/**
* Returns a Promsie that resolves to true if the given id exists for this model.
@@ -986,12 +986,12 @@ tsOut/model.js
*/
async exists(id) {
helpers_1.callbackError(...arguments);
- return !!(await typed_redis_helper_1.SISMEMBER(this.client, this.prefix('idsets'), id));
+ return !!(await typed_redis_helper_1.sismember(this.client, this.prefix('idsets'), id));
}
async getHashAll(id) {
const props = {};
- const values = await typed_redis_helper_1.HGETALL(this.client, `${this.prefix('hash')}:${id}`);
- if (values === null) {
+ const values = await typed_redis_helper_1.hgetall(this.client, `${this.prefix('hash')}:${id}`);
+ if (values === null || Object.keys(values).length === 0) {
throw new Error('not found');
}
Object.keys(values).forEach((key) => {
@@ -1156,15 +1156,15 @@ tsOut/model.js
multi = givenClient;
}
else if (givenClient) {
- multi = givenClient.MULTI();
+ multi = givenClient.multi();
}
else {
- multi = this.client.MULTI();
+ multi = this.client.multi();
}
// remove outstanding relation changes
this.relationChanges = [];
const relationKeysKey = `${this.rawPrefix().relationKeys}${this.modelName}:${this.id}`;
- const keys = await typed_redis_helper_1.SMEMBERS(this.client, relationKeysKey);
+ const keys = await typed_redis_helper_1.smembers(this.client, relationKeysKey);
debug(`Remvoing links for '%s.%s': %o.`, this.modelName, this.id, keys);
const others = keys.map((key) => {
const matches = key.match(/:([\w]*):([\w]*):[^:]+$/i);
@@ -1186,11 +1186,11 @@ tsOut/model.js
const otherRelationIdsPromises = others.map((item) => this.removeIdFromOtherRelations(multi, item));
await Promise.all(otherRelationIdsPromises);
// add multi'ed delete commands for other keys
- others.forEach((item) => multi.DEL(item.ownIdsKey));
+ others.forEach((item) => multi.del(item.ownIdsKey));
multi.del(relationKeysKey);
// if we didn't get a multi client from the callee we have to exec() ourselves
if (!this.isMultiClient(givenClient)) {
- await typed_redis_helper_1.EXEC(multi);
+ await typed_redis_helper_1.exec(multi);
}
}
/*
@@ -1200,9 +1200,9 @@ tsOut/model.js
Secondly it adds an SREM to the multi redis client.
*/
async removeIdFromOtherRelations(multi, item) {
- const ids = await typed_redis_helper_1.SMEMBERS(this.client, item.ownIdsKey);
+ const ids = await typed_redis_helper_1.smembers(this.client, item.ownIdsKey);
ids.forEach((id) => {
- multi.SREM(`${item.otherIdsKey}${id}`, this.stringId());
+ multi.srem(`${item.otherIdsKey}${id}`, this.stringId());
});
}
/**
@@ -1217,7 +1217,7 @@ tsOut/model.js
if (!this.id || !obj.id) {
throw new Error('Calling belongsTo() even though either the object itself or the relation does not have an id.');
}
- return !!(await typed_redis_helper_1.SISMEMBER(this.client, this.getRelationKey(obj.modelName, relationName), obj.id));
+ return !!(await typed_redis_helper_1.sismember(this.client, this.getRelationKey(obj.modelName, relationName), obj.id));
}
/**
* Returns an array of the ids of all objects that are linked with the given relation.
@@ -1231,7 +1231,7 @@ tsOut/model.js
throw new Error(`Calling getAll() even though this ${this.modelName} has no id. Please load or save it first.`);
}
const relationKey = this.getRelationKey(otherModelName, relationName);
- const ids = await typed_redis_helper_1.SMEMBERS(this.client, relationKey);
+ const ids = await typed_redis_helper_1.smembers(this.client, relationKey);
if (!Array.isArray(ids)) {
return [];
}
@@ -1253,7 +1253,7 @@ tsOut/model.js
throw new Error(`Calling numLinks() even though this ${this.modelName} has no id. Please load or save it first.`);
}
const relationKey = this.getRelationKey(otherModelName, relationName);
- return typed_redis_helper_1.SCARD(this.client, relationKey);
+ return typed_redis_helper_1.scard(this.client, relationKey);
}
/**
* Finds ids of objects by search arguments
@@ -1273,7 +1273,7 @@ tsOut/model.js
const onlyZSets = structuredSearches.filter((search) => search.type === 'zset');
if (onlySets.length === 0 && onlyZSets.length === 0) {
// no valid searches - return all ids
- return typed_redis_helper_1.SMEMBERS(this.client, `${this.prefix('idsets')}`);
+ return typed_redis_helper_1.smembers(this.client, `${this.prefix('idsets')}`);
}
debug(`Finding '%s's with these searches (sets, zsets):\n%o,\n%o.`, this.modelName, this.id, onlySets, onlyZSets);
const setPromises = this.setSearch(onlySets);
@@ -1338,7 +1338,7 @@ tsOut/model.js
}
async uniqueSearch(options) {
const key = `${this.prefix('unique')}:${options.key}:${options.value}`;
- const id = await typed_redis_helper_1.GET(this.client, key);
+ const id = await typed_redis_helper_1.get(this.client, key);
if (id) {
return [id];
}
@@ -1354,7 +1354,7 @@ tsOut/model.js
// shortcut
return [];
}
- return typed_redis_helper_1.SINTER(this.client, keys);
+ return typed_redis_helper_1.sinter(this.client, keys);
}
async zSetSearch(searches) {
const singleSearches = await Promise.all(searches.map((search) => this.singleZSetSearch(search)));
@@ -1363,12 +1363,12 @@ tsOut/model.js
singleZSetSearch(search) {
return new Promise((resolve, reject) => {
const key = `${this.prefix('scoredindex')}:${search.key}`;
- let command = 'ZRANGEBYSCORE';
+ let command = 'zrangebyscore';
const options = Object.assign({ endpoints: '[]', limit: -1, max: '+inf', min: '-inf', offset: 0 }, search.options);
if ((options.min === '+inf' && options.max !== '+inf') ||
(options.max === '-inf' && options.min !== '-inf') ||
parseFloat('' + options.min) > parseFloat('' + options.max)) {
- command = 'ZREVRANGEBYSCORE';
+ command = 'zrevrangebyscore';
}
if (options.endpoints === ')') {
options.endpoints = '[)';
@@ -1439,9 +1439,9 @@ tsOut/model.js
}
let idsetKey = this.prefix('idsets');
let zsetKey = `${this.prefix('scoredindex')}:${options.field}`;
- const client = this.client.MULTI();
+ const client = this.client.multi();
let tmpKey = '';
- debug(`Sorting '%s's with these options (alpha, direction, scored, start, stop, ids):`, this.modelName, this.id, alpha, direction, scored, start, stop, ids);
+ debug(`Sorting '%s's with these options (this.id, alpha, direction, scored, start, stop, ids):`, this.modelName, this.id, alpha, direction, scored, start, stop, ids);
if (ids) {
// to get the intersection of the given ids and all ids on the server we first
// temporarily store the given ids either in a set or sorted set and then return the intersection
@@ -1466,22 +1466,37 @@ tsOut/model.js
+new Date() +
Math.ceil(Math.random() * 1000);
ids.unshift(tmpKey);
- client.SADD.apply(client, ids); // typecast because rediss doesn't care about numbers/string
- client.SINTERSTORE([tmpKey, tmpKey, idsetKey]);
+ client.sadd.apply(client, ids); // typecast because rediss doesn't care about numbers/string
+ client.sinterstore([tmpKey, tmpKey, idsetKey]);
idsetKey = tmpKey;
}
}
if (scored) {
- const method = direction && direction === 'DESC' ? 'ZREVRANGE' : 'ZRANGE';
+ const method = direction && direction === 'DESC' ? 'zrevrange' : 'zrange';
client[method](zsetKey, start, stop);
}
else {
- client.sort(idsetKey, 'BY', `${this.prefix('hash')}:*->${options.field}`, 'LIMIT', String(start), String(stop), direction, alpha);
+ const args = [
+ idsetKey,
+ 'BY',
+ `${this.prefix('hash')}:*->${options.field}`,
+ 'LIMIT',
+ String(start),
+ String(stop),
+ direction,
+ ];
+ if (alpha) {
+ // due to the way ioredis handles input parameters, we have to do this weird apply and append thing.
+ // when just passing in an undefined value it throws syntax errors because it attempts to add that as an actual
+ // parameter
+ args.push(alpha);
+ }
+ client.sort.apply(client, args);
}
if (ids) {
client.del(tmpKey);
}
- const replies = await typed_redis_helper_1.EXEC(client);
+ const replies = await typed_redis_helper_1.exec(client);
let reply;
if (ids) {
// 2 redis commands to create the temp keys, then the query
diff --git a/docs/index.md b/docs/index.md
index 23e37e1..818cee6 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -109,14 +109,39 @@ nohm.setPrefix('yourAppPrefixForRedis');
You need to set a redis client for nohm. You should connect and select the appropriate database before you set it.
+##### node_redis
+
+The standard method would be to use node_redis:
+
```javascript
-const redisClient = require('redis').createClient();
+const redis = require('redis');
+const redisClient = redis.createClient();
// wait for redis to connect, to make sure everything will work as expected
redis.on('connect', () => {
nohm.setClient(redisClient);
});
```
+##### ioredis
+
+_(available in v2.2.0 onwards)_
+You can also use ioredis instead of node_redis.
+
+```javascript
+const Redis = require('ioredis');
+const redisClient = new Redis();
+// wait for ioredis to be ready, to make sure everything will work as expected
+redis.on('ready', () => {
+ nohm.setClient(redisClient);
+});
+```
+
+In the [simple stress tests](https://github.com/maritz/nohm/blob/master/extra/stress.js) using ioredis shows [more than a 20% decrease in performance](https://github.com/maritz/nohm/pull/138#issuecomment-417621930).
+
+If you require ioredis for other parts of your application and performance is important to you, the best option is probably to use ioredis for that and node\*redis for nohm. There _should_ be no conflicts when using both in the same application. Tread with caution though and test and benchmark it yourself for your use-cases.
+
+_Note: Clustering and sharding is not yet tested or officially supported by nohm._
+
#### Logging
By default nohm just logs errors it encounters to the console. However you can overwrite the logError method with anything you want:
diff --git a/extra/stress.js b/extra/stress.js
index bb8a4ba..15886e1 100644
--- a/extra/stress.js
+++ b/extra/stress.js
@@ -1,14 +1,71 @@
-var nohm = require(__dirname + '/../').Nohm,
- iterations = 10000;
+const nohm = require(__dirname + '/../').Nohm;
+let iterations = 10000;
-const redisClient = require('redis').createClient();
+let redisOptions = {};
-redisClient.once('connect', async () => {
+process.argv.forEach(function(val, index) {
+ if (val === '--nohm-prefix') {
+ redisOptions.prefix = process.argv[index + 1];
+ }
+ if (val === '--redis-host') {
+ redisOptions.redis_host = process.argv[index + 1];
+ }
+ if (val === '--redis-port') {
+ redisOptions.redis_port = process.argv[index + 1];
+ }
+ if (val === '--redis-auth') {
+ redisOptions.redis_auth = process.argv[index + 1];
+ }
+ if (val === '--iterations') {
+ iterations = parseInt(process.argv[index + 1], 10);
+ if (isNaN(iterations)) {
+ throw new Error(
+ `Invalid iterations argument: ${
+ process.argv[index + 1]
+ }. Must be a number.`,
+ );
+ }
+ }
+});
+
+let redisClient;
+
+const main = () => {
+ console.info('Connected to redis.');
+ stress().catch((error) => {
+ console.error('An error occured during benchmarking:', error);
+ process.exit(1);
+ });
+};
+
+if (process.env.NOHM_TEST_IOREDIS == 'true') {
+ console.info('Using ioredis for stress test');
+ const Redis = require('ioredis');
+
+ redisClient = new Redis({
+ port: redisOptions.redis_port,
+ host: redisOptions.redis_host,
+ password: redisOptions.redis_auth,
+ });
+ redisClient.once('ready', main);
+} else {
+ console.info('Using node_redis for stress test');
+ redisClient = require('redis').createClient(
+ redisOptions.redis_port,
+ redisOptions.redis_host,
+ {
+ auth_pass: redisOptions.redis_auth,
+ },
+ );
+ redisClient.once('connect', main);
+}
+
+const stress = async () => {
console.log(
`Starting stress test - saving and then updating ${iterations} models in parallel.`,
);
- nohm.setPrefix('nohm-stress-test');
+ nohm.setPrefix(redisOptions.prefix || 'nohm-stress-test');
nohm.setClient(redisClient);
var models = require(__dirname + '/models');
@@ -30,7 +87,7 @@ redisClient.once('connect', async () => {
counter++;
if (counter >= iterations) {
const updateTime = Date.now() - startUpdates;
- const timePerUpdate = iterations / updateTime * 1000;
+ const timePerUpdate = (iterations / updateTime) * 1000;
console.log(
`${updateTime}ms for ${counter} parallel User updates, ${timePerUpdate.toFixed(
2,
@@ -38,7 +95,7 @@ redisClient.once('connect', async () => {
);
console.log(`Total time: ${Date.now() - start}ms`);
console.log('Memory usage after', process.memoryUsage());
- redisClient.SCARD(
+ redisClient.scard(
`${nohm.prefix.idsets}${new UserModel().modelName}`,
async (err, numUsers) => {
if (err) {
@@ -96,7 +153,7 @@ redisClient.once('connect', async () => {
if (counter >= iterations) {
saveCallback = null;
const saveTime = Date.now() - start;
- const timePerSave = iterations / saveTime * 1000;
+ const timePerSave = (iterations / saveTime) * 1000;
console.log(
`${saveTime}ms for ${counter} parallel User saves, ${timePerSave.toFixed(
2,
@@ -115,4 +172,4 @@ redisClient.once('connect', async () => {
.catch(errorCallback);
users.push(user);
}
-});
+};
diff --git a/package-lock.json b/package-lock.json
index adc4de5..7776431 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -209,6 +209,15 @@
"@types/range-parser": "*"
}
},
+ "@types/ioredis": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/@types/ioredis/-/ioredis-4.0.1.tgz",
+ "integrity": "sha512-pvGlg7THjfjC6hzhOBVNtD0p+B0ph2FYx0wJjDBW7rpDLe9zazQgAt2WRWm3COW6Z0ZXqmCzPkq7WrOJWJklSQ==",
+ "dev": true,
+ "requires": {
+ "@types/node": "*"
+ }
+ },
"@types/lodash": {
"version": "4.14.112",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.112.tgz",
@@ -948,6 +957,12 @@
}
}
},
+ "cluster-key-slot": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.0.12.tgz",
+ "integrity": "sha512-21O0kGmvED5OJ7ZTdqQ5lQQ+sjuez33R+d35jZKLwqUb5mqcPHUsxOSzj61+LHVtxGZd1kShbQM3MjB/gBJkVg==",
+ "dev": true
+ },
"co": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
@@ -1605,6 +1620,12 @@
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
"dev": true
},
+ "denque": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/denque/-/denque-1.3.0.tgz",
+ "integrity": "sha512-4SRaSj+PqmrS1soW5/Avd7eJIM2JJIqLLmwhRqIGleZM/8KwZq80njbSS2Iqas+6oARkSkLDHEk4mm78q3JlIg==",
+ "dev": true
+ },
"diff": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz",
@@ -2216,6 +2237,12 @@
"write": "^0.2.1"
}
},
+ "flexbuffer": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/flexbuffer/-/flexbuffer-0.0.6.tgz",
+ "integrity": "sha1-A5/fI/iCPkQMOPMnfm/vEXQhWzA=",
+ "dev": true
+ },
"for-in": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
@@ -3321,6 +3348,46 @@
"integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=",
"dev": true
},
+ "ioredis": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-4.0.0.tgz",
+ "integrity": "sha512-KDio3eKM4zZWRPWlcM26E4Dcbj1bH6pPLNuCHJwKucklsEVMXT0axh5ctPaETbkPIBLRk910qKOEQoXSFkn+dw==",
+ "dev": true,
+ "requires": {
+ "cluster-key-slot": "^1.0.6",
+ "debug": "^3.1.0",
+ "denque": "^1.1.0",
+ "flexbuffer": "0.0.6",
+ "lodash.bind": "^4.2.1",
+ "lodash.clone": "^4.5.0",
+ "lodash.clonedeep": "^4.5.0",
+ "lodash.defaults": "^4.2.0",
+ "lodash.difference": "^4.5.0",
+ "lodash.flatten": "^4.4.0",
+ "lodash.foreach": "^4.5.0",
+ "lodash.isempty": "^4.4.0",
+ "lodash.partial": "^4.2.1",
+ "lodash.pick": "^4.4.0",
+ "lodash.sample": "^4.2.1",
+ "lodash.shuffle": "^4.2.0",
+ "lodash.values": "^4.3.0",
+ "redis-commands": "^1.2.0",
+ "redis-errors": "^1.2.0",
+ "redis-parser": "^3.0.0",
+ "standard-as-callback": "^1.0.0"
+ },
+ "dependencies": {
+ "redis-parser": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
+ "integrity": "sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=",
+ "dev": true,
+ "requires": {
+ "redis-errors": "^1.0.0"
+ }
+ }
+ }
+ },
"is-accessor-descriptor": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
@@ -3880,12 +3947,84 @@
"integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=",
"dev": true
},
+ "lodash.bind": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz",
+ "integrity": "sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU=",
+ "dev": true
+ },
+ "lodash.clone": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz",
+ "integrity": "sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y=",
+ "dev": true
+ },
+ "lodash.clonedeep": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
+ "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=",
+ "dev": true
+ },
"lodash.debounce": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
"integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=",
"dev": true
},
+ "lodash.defaults": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
+ "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=",
+ "dev": true
+ },
+ "lodash.difference": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz",
+ "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=",
+ "dev": true
+ },
+ "lodash.flatten": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
+ "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=",
+ "dev": true
+ },
+ "lodash.foreach": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz",
+ "integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=",
+ "dev": true
+ },
+ "lodash.isempty": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz",
+ "integrity": "sha1-b4bL7di+TsmHvpqvM8loTbGzHn4=",
+ "dev": true
+ },
+ "lodash.partial": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/lodash.partial/-/lodash.partial-4.2.1.tgz",
+ "integrity": "sha1-SfPYz9qjv/izqR0SfpIyRUGJYdQ=",
+ "dev": true
+ },
+ "lodash.pick": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz",
+ "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=",
+ "dev": true
+ },
+ "lodash.sample": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/lodash.sample/-/lodash.sample-4.2.1.tgz",
+ "integrity": "sha1-XkKRsMdT+hq+sKq4+ynfG2bwf20=",
+ "dev": true
+ },
+ "lodash.shuffle": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/lodash.shuffle/-/lodash.shuffle-4.2.0.tgz",
+ "integrity": "sha1-FFtQU8+HX29cKjP0i26ZSMbse0s=",
+ "dev": true
+ },
"lodash.template": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.4.0.tgz",
@@ -3905,6 +4044,12 @@
"lodash._reinterpolate": "~3.0.0"
}
},
+ "lodash.values": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/lodash.values/-/lodash.values-4.3.0.tgz",
+ "integrity": "sha1-o6bCsOvsxcLLocF+bmIP6BtT00c=",
+ "dev": true
+ },
"log-driver": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz",
@@ -6996,6 +7141,12 @@
"resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.3.5.tgz",
"integrity": "sha512-foGF8u6MXGFF++1TZVC6icGXuMYPftKXt1FBT2vrfU9ZATNtZJ8duRC5d1lEfE8hyVe3jhelHGB91oB7I6qLsA=="
},
+ "redis-errors": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
+ "integrity": "sha1-62LSrbFeTq9GEMBK/hUpOEJQq60=",
+ "dev": true
+ },
"redis-parser": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-2.6.0.tgz",
@@ -7573,6 +7724,12 @@
"integrity": "sha1-1PM6tU6OOHeLDKXP07OvsS22hiA=",
"dev": true
},
+ "standard-as-callback": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-1.0.1.tgz",
+ "integrity": "sha512-izxEITSyc7S+5oOiF/URiYaNkemPUxIndCNv66jJ548Y1TVxhBvioNMSPrZIQdaZDlhnguOdUzHA/7hJ3xFhuQ==",
+ "dev": true
+ },
"standard-version": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/standard-version/-/standard-version-4.4.0.tgz",
diff --git a/package.json b/package.json
index 8ca3523..7ac1949 100644
--- a/package.json
+++ b/package.json
@@ -46,6 +46,7 @@
},
"dependencies": {
"debug": "^3.1.0",
+ "ioredis": "^4.0.0",
"lodash": "^4.17.10",
"redis": "^2.8.0",
"traverse": "^0.6.6",
@@ -55,6 +56,7 @@
"@types/async": "^2.0.47",
"@types/debug": "0.0.30",
"@types/express": "^4.16.0",
+ "@types/ioredis": "^4.0.1",
"@types/lodash": "^4.14.112",
"@types/node": "^10.5.2",
"@types/redis": "^2.8.4",
diff --git a/test/featureTests.js b/test/featureTests.js
index 0ae8fd9..a8c0593 100644
--- a/test/featureTests.js
+++ b/test/featureTests.js
@@ -937,14 +937,15 @@ exports.deleteNonExistant = async (t) => {
try {
await user.remove();
+ t.fail('Removing a user that should not exist did not throw an error.');
} catch (err) {
t.same(
err,
new Error('not found'),
'Trying to delete an instance that doesn\'t exist did not return "not found".',
);
- t.done();
}
+ t.done();
};
const MethodOverwrite = nohm.model('methodOverwrite', {
diff --git a/test/findTests.js b/test/findTests.js
index 1aaa8d0..a8cd59d 100644
--- a/test/findTests.js
+++ b/test/findTests.js
@@ -116,6 +116,7 @@ exports.find = {
email: 'numericindextest@hurgel.de',
gender: 'male',
number: 3,
+ numberNonIndexed: 4,
},
{
// id: 2
@@ -124,6 +125,7 @@ exports.find = {
gender: 'male',
number: 4,
number2: 33,
+ numberNonIndexed: 4,
},
{
// id: 3
@@ -132,33 +134,39 @@ exports.find = {
gender: 'female',
number: 4,
number2: 1,
+ numberNonIndexed: 1,
},
{
// id: 4
name: 'uniquefind',
email: 'uniquefind@hurgel.de',
+ numberNonIndexed: 5,
},
{
// id: 5
name: 'indextest',
email: 'indextest@hurgel.de',
+ numberNonIndexed: 8,
},
{
// id: 6
name: 'indextest',
email: 'indextest2@hurgel.de',
+ numberNonIndexed: 200,
},
{
// id: 7
name: 'a_sort_first',
email: 'a_sort_first@hurgel.de',
number: 1,
+ numberNonIndexed: 0,
},
{
// id: 8
name: 'z_sort_last',
email: 'z_sort_last@hurgel.de',
number: 100000,
+ numberNonIndexed: 24.5,
},
],
function(users, ids) {
diff --git a/test/pubsubTests.js b/test/pubsubTests.js
index d8de599..6665c9e 100644
--- a/test/pubsubTests.js
+++ b/test/pubsubTests.js
@@ -35,15 +35,21 @@ module.exports = {
'set/get pubSub client': async (t) => {
t.expect(2);
await nohm.setPubSubClient(secondaryClient);
- t.same(
- nohm.getPubSubClient(),
- secondaryClient,
- "Second redis client wasn't set properly",
- );
- t.ok(
- nohm.getPubSubClient().subscription_set,
- "Second redis client isn't subscribed to anything",
- );
+ const client = nohm.getPubSubClient();
+ t.same(client, secondaryClient, "Second redis client wasn't set properly");
+ const isIoRedis =
+ client.connected === undefined && client.status === 'ready';
+ if (isIoRedis) {
+ t.ok(
+ client.condition.subscriber !== false,
+ "Second redis client isn't subscribed to anything",
+ );
+ } else {
+ t.ok(
+ client.subscription_set,
+ "Second redis client isn't subscribed to anything",
+ );
+ }
t.done();
},
diff --git a/test/redisHelperTests.js b/test/redisHelperTests.js
index 915e3d3..471123e 100644
--- a/test/redisHelperTests.js
+++ b/test/redisHelperTests.js
@@ -57,25 +57,25 @@ const testCommand = (name, firstArg, secondArg, resolveExpect) => {
};
};
-exports['GET'] = testCommand('GET', 'foo', undefined, 'bar');
-exports['EXISTS'] = testCommand('EXISTS', 'foo', undefined, true);
-exports['DEL'] = testCommand('DEL', 'foo');
-exports['SET'] = testCommand('SET', 'foo', 'bar');
-exports['MSET'] = testCommand('MSET', 'foo', ['foo', 'bar']);
-exports['SETNX'] = testCommand('SETNX', 'foo', 'bar', true);
-exports['SMEMBERS'] = testCommand('SMEMBERS', 'foo', undefined, ['bar']);
-exports['SISMEMBER'] = testCommand('SISMEMBER', 'foo', 'bar', 1);
-exports['SADD'] = testCommand('SADD', 'foo', 'bar', 1);
-exports['SINTER'] = testCommand('SINTER', 'foo', 'bar', ['bar', 'baz']);
-exports['HGETALL'] = testCommand('HGETALL', 'foo', undefined, ['bar', 'baz']);
-exports['PSUBSCRIBE'] = testCommand('PSUBSCRIBE', 'foo', ['bar', 'baz']);
-exports['PUNSUBSCRIBE'] = testCommand('PUNSUBSCRIBE', 'foo', ['bar', 'baz']);
+exports['get'] = testCommand('get', 'foo', undefined, 'bar');
+exports['exists'] = testCommand('exists', 'foo', undefined, true);
+exports['del'] = testCommand('del', 'foo');
+exports['set'] = testCommand('set', 'foo', 'bar');
+exports['mset'] = testCommand('mset', 'foo', ['foo', 'bar']);
+exports['setnx'] = testCommand('setnx', 'foo', 'bar', true);
+exports['smembers'] = testCommand('smembers', 'foo', undefined, ['bar']);
+exports['sismember'] = testCommand('sismember', 'foo', 'bar', 1);
+exports['sadd'] = testCommand('sadd', 'foo', 'bar', 1);
+exports['sinter'] = testCommand('sinter', 'foo', 'bar', ['bar', 'baz']);
+exports['hgetall'] = testCommand('hgetall', 'foo', undefined, ['bar', 'baz']);
+exports['psubscribe'] = testCommand('psubscribe', 'foo', ['bar', 'baz']);
+exports['punsubscribe'] = testCommand('punsubscribe', 'foo', ['bar', 'baz']);
-exports['EXEC'] = async (t) => {
+exports['exec'] = async (t) => {
t.expect(4);
- // EXEC has no firstArg. it's easier to duplicate the test here instead of changing testCommand
- const name = 'EXEC';
+ // exec has no firstArg. it's easier to duplicate the test here instead of changing testCommand
+ const name = 'exec';
const resolveExpect = ['foo', 'baz'];
const testMethod = redisHelper[name];
diff --git a/test/regressions.js b/test/regressions.js
index a8dac41..702614c 100644
--- a/test/regressions.js
+++ b/test/regressions.js
@@ -51,7 +51,7 @@ exports['#114 update does not reset index'] = async (t) => {
});
await instance2Activated.save();
- const membersTrue = await redisPromise.SMEMBERS(
+ const membersTrue = await redisPromise.smembers(
redis,
`${instance2Activated.prefix('index')}:isActive:true`,
);
@@ -62,14 +62,14 @@ exports['#114 update does not reset index'] = async (t) => {
'Not all instances were properly indexed as isActive:true',
);
- const membersFalse = await redisPromise.SMEMBERS(
+ const membersFalse = await redisPromise.smembers(
redis,
`${instance2Activated.prefix('index')}:isActive:false`,
);
t.same(membersFalse, [], 'An index for isActive:false remained.');
- const uniqueExists = await redisPromise.EXISTS(
+ const uniqueExists = await redisPromise.exists(
redis,
`${instance2Activated.prefix('unique')}:uniqueDeletion:two`,
);
diff --git a/test/testArgs.js b/test/testArgs.js
index d5e9f15..2120280 100644
--- a/test/testArgs.js
+++ b/test/testArgs.js
@@ -23,36 +23,36 @@ process.argv.forEach(function(val, index) {
}
});
-exports.redis = require('redis').createClient(
- exports.redis_port,
- exports.redis_host,
- {
- auth_pass: exports.redis_auth,
- },
-);
+if (process.env.NOHM_TEST_IOREDIS == 'true') {
+ console.info('Using ioredis for tests');
+ const Redis = require('ioredis');
-exports.secondaryClient = require('redis').createClient(
- exports.redis_port,
- exports.redis_host,
- {
- auth_pass: exports.redis_auth,
- },
-);
+ exports.redis = new Redis({
+ port: exports.redis_port,
+ host: exports.redis_host,
+ password: exports.redis_auth,
+ });
-/**
- *
-const Redis = require('ioredis');
-const client = exports.redis = new Redis({
- port: exports.port,
- host: exports.host,
- password: exports.redis_auth,
-});
-
-client.connected = false;
+ exports.secondaryClient = new Redis({
+ port: exports.redis_port,
+ host: exports.redis_host,
+ password: exports.redis_auth,
+ });
+} else {
+ console.info('Using node_redis for tests');
+ exports.redis = require('redis').createClient(
+ exports.redis_port,
+ exports.redis_host,
+ {
+ auth_pass: exports.redis_auth,
+ },
+ );
-setInterval(() => {
- if (!client.connected && client.status === 'ready') {
- client.connected = true;
- }
-})
- */
+ exports.secondaryClient = require('redis').createClient(
+ exports.redis_port,
+ exports.redis_host,
+ {
+ auth_pass: exports.redis_auth,
+ },
+ );
+}
diff --git a/ts/index.ts b/ts/index.ts
index f3c2d25..6ed3163 100644
--- a/ts/index.ts
+++ b/ts/index.ts
@@ -33,7 +33,7 @@ import {
timestampProperty,
TTypedDefinitions,
} from './model.header';
-import { PSUBSCRIBE, PUNSUBSCRIBE } from './typed-redis-helper';
+import { psubscribe, punsubscribe } from './typed-redis-helper';
export {
boolProperty,
@@ -217,13 +217,17 @@ export class NohmClass {
public setClient(client?: redis.RedisClient) {
debug(
'Setting new redis client. Connected: %s; Address: %s.',
- client && client.connected,
+ client && (client.connected || (client as any).status === 'ready'),
client && (client as any).address,
);
- if (client && !client.connected) {
+ // ioredis uses .status string instead of .connected boolean
+ if (client && !(client.connected || (client as any).status === 'ready')) {
this
.logError(`WARNING: setClient() received a redis client that is not connected yet.
- Consider waiting for an established connection before setting it.`);
+ Consider waiting for an established connection before setting it. Status (if ioredis): ${
+ (client as any).status
+ }
+ , connected (if node_redis): ${client.connected}`);
} else if (!client) {
client = redis.createClient();
}
@@ -816,13 +820,13 @@ export class NohmClass {
public async purgeDb(client: redis.RedisClient = this.client): Promise {
function delKeys(prefix: string) {
return new Promise((resolve, reject) => {
- client.KEYS(prefix + '*', (err, keys) => {
+ client.keys(prefix + '*', (err, keys) => {
if (err) {
reject(err);
} else if (keys.length === 0) {
resolve();
} else {
- client.DEL(keys, (innerErr) => {
+ client.del(keys, (innerErr) => {
if (innerErr) {
reject(innerErr);
} else {
@@ -919,7 +923,7 @@ export class NohmClass {
this.publishEventEmitter.setMaxListeners(0); // TODO: check if this is sensible
this.isPublishSubscribed = true;
- await PSUBSCRIBE(
+ await psubscribe(
this.publishClient,
this.prefix.channel + PUBSUB_ALL_PATTERN,
);
@@ -1002,7 +1006,7 @@ export class NohmClass {
this.prefix.channel + PUBSUB_ALL_PATTERN,
);
this.isPublishSubscribed = false;
- await PUNSUBSCRIBE(
+ await punsubscribe(
this.publishClient,
this.prefix.channel + PUBSUB_ALL_PATTERN,
);
diff --git a/ts/model.ts b/ts/model.ts
index 2ba77c2..9a9f6c0 100644
--- a/ts/model.ts
+++ b/ts/model.ts
@@ -35,19 +35,19 @@ import {
TValidationDefinition,
} from './model.header';
import {
- DEL,
- EXEC,
- EXISTS,
- GET,
- HGETALL,
- MSET,
- SADD,
- SCARD,
- SET,
- SETNX,
- SINTER,
- SISMEMBER,
- SMEMBERS,
+ del,
+ exec,
+ exists,
+ get,
+ hgetall,
+ mset,
+ sadd,
+ scard,
+ set,
+ setnx,
+ sinter,
+ sismember,
+ smembers,
} from './typed-redis-helper';
import { validators } from './validators';
@@ -274,7 +274,7 @@ abstract class NohmModel {
});
try {
- const dbVersion = await GET(this.client, versionKey);
+ const dbVersion = await get(this.client, versionKey);
if (this.meta.version !== dbVersion) {
const generator = this.options.idGenerator || 'default';
debug(
@@ -285,9 +285,9 @@ abstract class NohmModel {
properties,
);
await Promise.all([
- SET(this.client, idGeneratorKey, generator.toString()),
- SET(this.client, propertiesKey, JSON.stringify(properties)),
- SET(this.client, versionKey, this.meta.version),
+ set(this.client, idGeneratorKey, generator.toString()),
+ set(this.client, propertiesKey, JSON.stringify(properties)),
+ set(this.client, versionKey, this.meta.version),
]);
}
this.meta.inDb = true;
@@ -625,7 +625,7 @@ abstract class NohmModel {
}
let numIdExisting: number = 0;
if (action !== 'create') {
- numIdExisting = await SISMEMBER(
+ numIdExisting = await sismember(
this.client,
this.prefix('idsets'),
this.id,
@@ -653,7 +653,7 @@ abstract class NohmModel {
private async create() {
const id = await this.generateId();
- await SADD(this.client, this.prefix('idsets'), id);
+ await sadd(this.client, this.prefix('idsets'), id);
await this.setUniqueIds(id);
this.id = id;
}
@@ -709,7 +709,7 @@ abstract class NohmModel {
this.id,
id,
);
- return MSET(this.client, mSetArguments);
+ return mset(this.client, mSetArguments);
}
}
@@ -721,7 +721,7 @@ abstract class NohmModel {
}
const hmSetArguments = [];
- const client = this.client.MULTI();
+ const client = this.client.multi();
const isCreate = !this.inDb;
hmSetArguments.push(`${this.prefix('hash')}:${this.id}`);
@@ -734,12 +734,12 @@ abstract class NohmModel {
if (hmSetArguments.length > 1) {
hmSetArguments.push('__meta_version', this.meta.version);
- client.HMSET.apply(client, hmSetArguments);
+ client.hmset.apply(client, hmSetArguments);
}
await this.setIndices(client);
- await EXEC(client);
+ await exec(client);
const linkResults = await this.storeLinks(options);
this.relationChanges = [];
@@ -855,7 +855,7 @@ abstract class NohmModel {
const command = change.action === 'link' ? 'sadd' : 'srem';
const relationKeyPrefix = this.rawPrefix().relationKeys;
- const multi = this.client.MULTI();
+ const multi = this.client.multi();
// relation to other
const toKey = this.getRelationKey(
change.object.modelName,
@@ -876,7 +876,7 @@ abstract class NohmModel {
try {
debug(`Linking in redis.`, this.modelName, change.options.name, command);
- await EXEC(multi);
+ await exec(multi);
if (!change.options.silent) {
this.fireEvent(change.action, change.object, change.options.name);
}
@@ -922,7 +922,7 @@ abstract class NohmModel {
prop.__updated,
this.inDb,
);
- multi.DEL(
+ multi.del(
`${this.prefix('unique')}:${key}:${oldUniqueValue}`,
this.nohmClass.logError,
);
@@ -940,7 +940,7 @@ abstract class NohmModel {
);
// we use scored sets for things like "get all users older than 5"
const scoredPrefix = this.prefix('scoredindex');
- multi.ZADD(
+ multi.zadd(
`${scoredPrefix}:${key}`,
prop.value,
this.stringId(),
@@ -958,13 +958,13 @@ abstract class NohmModel {
);
const prefix = this.prefix('index');
if (isInDb) {
- multi.SREM(
+ multi.srem(
`${prefix}:${key}:${oldValue}`,
this.stringId(),
this.nohmClass.logError,
);
}
- multi.SADD(
+ multi.sadd(
`${prefix}:${key}:${prop.value}`,
this.stringId(),
this.nohmClass.logError,
@@ -1155,9 +1155,9 @@ abstract class NohmModel {
* We lock the unique value here if it's not locked yet, then later remove the old uniquelock
* when really saving it. (or we free the unique slot if we're not saving)
*/
- dbValue = await SETNX(this.client, key, this.stringId());
+ dbValue = await setnx(this.client, key, this.stringId());
} else {
- dbValue = await EXISTS(this.client, key);
+ dbValue = await exists(this.client, key);
}
let isFreeUnique = false;
if (setDirectly && dbValue === 1) {
@@ -1171,7 +1171,7 @@ abstract class NohmModel {
// setDirectly === true means using setnx which returns 1 if the value did *not* exist
// if it did exist, we check if the unique is the same as the one on this model.
// see https://github.com/maritz/nohm/issues/82 for use-case
- const dbId = await GET(this.client, key);
+ const dbId = await get(this.client, key);
if (dbId === this.stringId()) {
isFreeUnique = true;
}
@@ -1246,7 +1246,7 @@ abstract class NohmModel {
this.id,
);
const deletes: Array> = this.tmpUniqueKeys.map((key) => {
- return DEL(this.client, key);
+ return del(this.client, key);
});
await Promise.all(deletes);
}
@@ -1281,7 +1281,7 @@ abstract class NohmModel {
private async deleteDbCall(): Promise {
// TODO: write test for removal of relationKeys - purgeDb kinda tests it already, but not enough
- const multi = this.client.MULTI();
+ const multi = this.client.multi();
multi.del(`${this.prefix('hash')}:${this.stringId()}`);
multi.srem(this.prefix('idsets'), this.stringId());
@@ -1307,7 +1307,7 @@ abstract class NohmModel {
await this.unlinkAll(multi);
- await EXEC(multi);
+ await exec(multi);
}
/**
@@ -1318,13 +1318,13 @@ abstract class NohmModel {
*/
public async exists(id: any): Promise {
callbackError(...arguments);
- return !!(await SISMEMBER(this.client, this.prefix('idsets'), id));
+ return !!(await sismember(this.client, this.prefix('idsets'), id));
}
private async getHashAll(id: any): Promise> {
const props: Partial = {};
- const values = await HGETALL(this.client, `${this.prefix('hash')}:${id}`);
- if (values === null) {
+ const values = await hgetall(this.client, `${this.prefix('hash')}:${id}`);
+ if (values === null || Object.keys(values).length === 0) {
throw new Error('not found');
}
Object.keys(values).forEach((key) => {
@@ -1536,9 +1536,9 @@ abstract class NohmModel {
if (this.isMultiClient(givenClient)) {
multi = givenClient;
} else if (givenClient) {
- multi = givenClient.MULTI();
+ multi = givenClient.multi();
} else {
- multi = this.client.MULTI();
+ multi = this.client.multi();
}
// remove outstanding relation changes
@@ -1547,7 +1547,7 @@ abstract class NohmModel {
this.modelName
}:${this.id}`;
- const keys = await SMEMBERS(this.client, relationKeysKey);
+ const keys = await smembers(this.client, relationKeysKey);
debug(`Remvoing links for '%s.%s': %o.`, this.modelName, this.id, keys);
@@ -1579,12 +1579,12 @@ abstract class NohmModel {
await Promise.all(otherRelationIdsPromises);
// add multi'ed delete commands for other keys
- others.forEach((item) => multi.DEL(item.ownIdsKey));
+ others.forEach((item) => multi.del(item.ownIdsKey));
multi.del(relationKeysKey);
// if we didn't get a multi client from the callee we have to exec() ourselves
if (!this.isMultiClient(givenClient)) {
- await EXEC(multi);
+ await exec(multi);
}
}
@@ -1598,9 +1598,9 @@ abstract class NohmModel {
multi: redis.Multi,
item: IUnlinkKeyMapItem,
): Promise {
- const ids = await SMEMBERS(this.client, item.ownIdsKey);
+ const ids = await smembers(this.client, item.ownIdsKey);
ids.forEach((id) => {
- multi.SREM(`${item.otherIdsKey}${id}`, this.stringId());
+ multi.srem(`${item.otherIdsKey}${id}`, this.stringId());
});
}
@@ -1621,7 +1621,7 @@ abstract class NohmModel {
'Calling belongsTo() even though either the object itself or the relation does not have an id.',
);
}
- return !!(await SISMEMBER(
+ return !!(await sismember(
this.client,
this.getRelationKey(obj.modelName, relationName),
obj.id,
@@ -1647,7 +1647,7 @@ abstract class NohmModel {
);
}
const relationKey = this.getRelationKey(otherModelName, relationName);
- const ids = await SMEMBERS(this.client, relationKey);
+ const ids = await smembers(this.client, relationKey);
if (!Array.isArray(ids)) {
return [];
} else {
@@ -1676,7 +1676,7 @@ abstract class NohmModel {
);
}
const relationKey = this.getRelationKey(otherModelName, relationName);
- return SCARD(this.client, relationKey);
+ return scard(this.client, relationKey);
}
/**
@@ -1721,7 +1721,7 @@ abstract class NohmModel {
if (onlySets.length === 0 && onlyZSets.length === 0) {
// no valid searches - return all ids
- return SMEMBERS(this.client, `${this.prefix('idsets')}`);
+ return smembers(this.client, `${this.prefix('idsets')}`);
}
debug(
`Finding '%s's with these searches (sets, zsets):\n%o,\n%o.`,
@@ -1808,7 +1808,7 @@ abstract class NohmModel {
options: IStructuredSearch,
): Promise> {
const key = `${this.prefix('unique')}:${options.key}:${options.value}`;
- const id = await GET(this.client, key);
+ const id = await get(this.client, key);
if (id) {
return [id];
} else {
@@ -1826,7 +1826,7 @@ abstract class NohmModel {
// shortcut
return [];
}
- return SINTER(this.client, keys);
+ return sinter(this.client, keys);
}
private async zSetSearch(
@@ -1843,7 +1843,7 @@ abstract class NohmModel {
): Promise> {
return new Promise((resolve, reject) => {
const key = `${this.prefix('scoredindex')}:${search.key}`;
- let command: 'ZRANGEBYSCORE' | 'ZREVRANGEBYSCORE' = 'ZRANGEBYSCORE';
+ let command: 'zrangebyscore' | 'zrevrangebyscore' = 'zrangebyscore';
const options: ISearchOption = {
endpoints: '[]',
limit: -1,
@@ -1857,7 +1857,7 @@ abstract class NohmModel {
(options.max === '-inf' && options.min !== '-inf') ||
parseFloat('' + options.min) > parseFloat('' + options.max)
) {
- command = 'ZREVRANGEBYSCORE';
+ command = 'zrevrangebyscore';
}
if (options.endpoints === ')') {
@@ -1953,11 +1953,11 @@ abstract class NohmModel {
}
let idsetKey = this.prefix('idsets');
let zsetKey = `${this.prefix('scoredindex')}:${options.field}`;
- const client: redis.Multi = this.client.MULTI();
+ const client: redis.Multi = this.client.multi();
let tmpKey: string = '';
debug(
- `Sorting '%s's with these options (alpha, direction, scored, start, stop, ids):`,
+ `Sorting '%s's with these options (this.id, alpha, direction, scored, start, stop, ids):`,
this.modelName,
this.id,
alpha,
@@ -1991,16 +1991,16 @@ abstract class NohmModel {
+new Date() +
Math.ceil(Math.random() * 1000);
ids.unshift(tmpKey);
- client.SADD.apply(client, ids as Array); // typecast because rediss doesn't care about numbers/string
- client.SINTERSTORE([tmpKey, tmpKey, idsetKey]);
+ client.sadd.apply(client, ids as Array); // typecast because rediss doesn't care about numbers/string
+ client.sinterstore([tmpKey, tmpKey, idsetKey]);
idsetKey = tmpKey;
}
}
if (scored) {
- const method = direction && direction === 'DESC' ? 'ZREVRANGE' : 'ZRANGE';
+ const method = direction && direction === 'DESC' ? 'zrevrange' : 'zrange';
client[method](zsetKey, start, stop);
} else {
- client.sort(
+ const args = [
idsetKey,
'BY',
`${this.prefix('hash')}:*->${options.field}`,
@@ -2008,13 +2008,19 @@ abstract class NohmModel {
String(start),
String(stop),
direction,
- alpha as any, // any casting because passing in an empty string actually results in errors in some cases
- );
+ ];
+ if (alpha) {
+ // due to the way ioredis handles input parameters, we have to do this weird apply and append thing.
+ // when just passing in an undefined value it throws syntax errors because it attempts to add that as an actual
+ // parameter
+ args.push(alpha);
+ }
+ client.sort.apply(client, args);
}
if (ids) {
client.del(tmpKey);
}
- const replies = await EXEC(client);
+ const replies = await exec(client);
let reply: Array;
if (ids) {
// 2 redis commands to create the temp keys, then the query
diff --git a/ts/typed-redis-helper.ts b/ts/typed-redis-helper.ts
index 2d6c3fd..f5eacb3 100644
--- a/ts/typed-redis-helper.ts
+++ b/ts/typed-redis-helper.ts
@@ -1,14 +1,15 @@
import { Multi, RedisClient } from 'redis';
+import * as IORedis from 'ioredis';
export const errorMessage =
'Supplied redis client does not have the correct methods.';
-export function GET(client: RedisClient | Multi, key: string): Promise {
+export function get(client: RedisClient | Multi, key: string): Promise {
return new Promise((resolve, reject) => {
- if (!client.GET) {
+ if (!client.get) {
return reject(new Error(errorMessage));
}
- client.GET(key, (err, value) => {
+ client.get(key, (err, value) => {
if (err) {
reject(err);
} else {
@@ -18,15 +19,15 @@ export function GET(client: RedisClient | Multi, key: string): Promise {
});
}
-export function EXISTS(
+export function exists(
client: RedisClient | Multi,
key: string,
): Promise {
return new Promise((resolve, reject) => {
- if (!client.EXISTS) {
+ if (!client.exists) {
return reject(new Error(errorMessage));
}
- client.EXISTS(key, (err, reply) => {
+ client.exists(key, (err, reply) => {
if (err) {
reject(err);
} else {
@@ -36,12 +37,12 @@ export function EXISTS(
});
}
-export function DEL(client: RedisClient | Multi, key: string): Promise {
+export function del(client: RedisClient | Multi, key: string): Promise {
return new Promise((resolve, reject) => {
- if (!client.DEL) {
+ if (!client.del) {
return reject(new Error(errorMessage));
}
- client.DEL(key, (err) => {
+ client.del(key, (err) => {
if (err) {
reject(err);
} else {
@@ -51,16 +52,16 @@ export function DEL(client: RedisClient | Multi, key: string): Promise {
});
}
-export function SET(
+export function set(
client: RedisClient | Multi,
key: string,
value: string,
): Promise {
return new Promise((resolve, reject) => {
- if (!client.SET) {
+ if (!client.set) {
return reject(new Error(errorMessage));
}
- client.SET(key, value, (err) => {
+ client.set(key, value, (err) => {
if (err) {
reject(err);
} else {
@@ -70,16 +71,16 @@ export function SET(
});
}
-export function MSET(
+export function mset(
client: RedisClient | Multi,
keyValueArrayOrString: string | Array,
...keyValuePairs: Array
): Promise {
return new Promise((resolve, reject) => {
- if (!client.MSET) {
+ if (!client.mset) {
return reject(new Error(errorMessage));
}
- client.MSET.apply(client, [
+ client.mset.apply(client, [
keyValueArrayOrString,
...keyValuePairs,
(err: Error | null) => {
@@ -93,16 +94,16 @@ export function MSET(
});
}
-export function SETNX(
+export function setnx(
client: RedisClient | Multi,
key: string,
value: string,
): Promise {
return new Promise((resolve, reject) => {
- if (!client.SETNX) {
+ if (!client.setnx) {
return reject(new Error(errorMessage));
}
- client.SETNX(key, value, (err, reply) => {
+ client.setnx(key, value, (err, reply) => {
if (err) {
reject(err);
} else {
@@ -112,15 +113,15 @@ export function SETNX(
});
}
-export function SMEMBERS(
+export function smembers(
client: RedisClient | Multi,
key: string,
): Promise> {
return new Promise>((resolve, reject) => {
- if (!client.SMEMBERS) {
+ if (!client.smembers) {
return reject(new Error(errorMessage));
}
- client.SMEMBERS(key, (err, values) => {
+ client.smembers(key, (err, values) => {
if (err) {
reject(err);
} else {
@@ -130,15 +131,15 @@ export function SMEMBERS(
});
}
-export function SCARD(
+export function scard(
client: RedisClient | Multi,
key: string,
): Promise {
return new Promise((resolve, reject) => {
- if (!client.SCARD) {
+ if (!client.scard) {
return reject(new Error(errorMessage));
}
- client.SCARD(key, (err, value) => {
+ client.scard(key, (err, value) => {
if (err) {
reject(err);
} else {
@@ -148,16 +149,16 @@ export function SCARD(
});
}
-export function SISMEMBER(
+export function sismember(
client: RedisClient | Multi,
key: string,
value: string,
): Promise {
return new Promise((resolve, reject) => {
- if (!client.SISMEMBER) {
+ if (!client.sismember) {
return reject(new Error(errorMessage));
}
- client.SISMEMBER(key, value, (err, numFound) => {
+ client.sismember(key, value, (err, numFound) => {
if (err) {
reject(err);
} else {
@@ -167,16 +168,16 @@ export function SISMEMBER(
});
}
-export function SADD(
+export function sadd(
client: RedisClient | Multi,
key: string,
value: string,
): Promise {
return new Promise((resolve, reject) => {
- if (!client.SADD) {
+ if (!client.sadd) {
return reject(new Error(errorMessage));
}
- client.SADD(key, value, (err, numInserted) => {
+ client.sadd(key, value, (err, numInserted) => {
if (err) {
reject(err);
} else {
@@ -186,16 +187,16 @@ export function SADD(
});
}
-export function SINTER(
+export function sinter(
client: RedisClient | Multi,
keyArrayOrString: string | Array,
...keys: Array
): Promise> {
return new Promise>((resolve, reject) => {
- if (!client.SINTER) {
+ if (!client.sinter) {
return reject(new Error(errorMessage));
}
- client.SINTER.apply(client, [
+ client.sinter.apply(client, [
keyArrayOrString,
...keys,
(err: Error | null, values: Array) => {
@@ -209,15 +210,15 @@ export function SINTER(
});
}
-export function HGETALL(
+export function hgetall(
client: RedisClient | Multi,
key: string,
): Promise<{ [key: string]: string }> {
return new Promise<{ [key: string]: string }>((resolve, reject) => {
- if (!client.HGETALL) {
+ if (!client.hgetall) {
return reject(new Error(errorMessage));
}
- client.HGETALL(key, (err, values) => {
+ client.hgetall(key, (err, values) => {
if (err) {
reject(err);
} else {
@@ -227,31 +228,48 @@ export function HGETALL(
});
}
-export function EXEC(client: Multi): Promise> {
+export function exec(client: Multi): Promise> {
return new Promise>((resolve, reject) => {
- if (!client.EXEC) {
+ if (!client.exec) {
return reject(new Error(errorMessage));
}
- client.EXEC((err, results) => {
+ client.exec((err, results) => {
if (err) {
return reject(err);
} else {
+ // detect if it's ioredis, which has a different return structure.
+ // better methods for doing this would be very welcome!
+ if (
+ Array.isArray(results[0]) &&
+ (results[0][0] === null ||
+ // once ioredis has proper typings, this any casting can be changed
+ results[0][0] instanceof (IORedis as any).ReplyError)
+ ) {
+ // transform ioredis format to node_redis format
+ results = results.map((result: Array) => {
+ const error = result[0];
+ if (error instanceof (IORedis as any).ReplyError) {
+ return error.message;
+ }
+ return result[1];
+ });
+ }
resolve(results);
}
});
});
}
-export function PSUBSCRIBE(
+export function psubscribe(
client: RedisClient,
patternOrPatternArray: string | Array,
...patterns: Array
): Promise {
return new Promise((resolve, reject) => {
- if (!client.PSUBSCRIBE) {
+ if (!client.psubscribe) {
return reject(new Error(errorMessage));
}
- client.PSUBSCRIBE.apply(client, [
+ client.psubscribe.apply(client, [
patternOrPatternArray,
...patterns,
(err: Error | null) => {
@@ -265,16 +283,16 @@ export function PSUBSCRIBE(
});
}
-export function PUNSUBSCRIBE(
+export function punsubscribe(
client: RedisClient,
patternOrPatternArray: string | Array,
...patterns: Array
): Promise {
return new Promise((resolve, reject) => {
- if (!client.PUNSUBSCRIBE) {
+ if (!client.punsubscribe) {
return reject(new Error(errorMessage));
}
- client.PUNSUBSCRIBE.apply(client, [
+ client.punsubscribe.apply(client, [
patternOrPatternArray,
...patterns,
(err: Error | null) => {