Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/jaredwray/keyv
Browse files Browse the repository at this point in the history
  • Loading branch information
jaredwray committed Oct 9, 2024
2 parents ec311c1 + 53b5928 commit 835e6cb
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 38 deletions.
38 changes: 22 additions & 16 deletions packages/valkey/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,31 @@ Valkey is the open source replacement to Redis which decided do a [dual license]

We are using the [iovalkey](https://www.npmjs.com/package/iovalkey) which is a Node.js client for Valkey based on the `ioredis` client.

## Install
# Install

```shell
npm install --save keyv @keyv/valkey
```

## Usage
# Usage

This is using the helper `createKeyv` function to create a Keyv instance with the Valkey storage adapter:

```js
import Keyv from 'keyv';
import KeyvValkey from '@keyv/valkey';
import {createKeyv} from '@keyv/valkey';

const keyv = new Keyv(new KeyvValkey('redis://user:pass@localhost:6379'));
const keyv = createKeyv('redis://localhost:6379');
keyv.on('error', handleConnectionError);
await keyv.set('foo', 'bar');
console.log(await keyv.get('foo')); // 'bar'
```

e.g:
If you want to specify the `KeyvValkey` class directly, you can do so:

```js
import Keyv from 'keyv';
import KeyvValkey from '@keyv/valkey';

const keyv = new Keyv(new KeyvValkey('redis://user:pass@localhost:6379', { disable_resubscribing: true }));
```

Expand Down Expand Up @@ -68,32 +74,32 @@ const redis = new Redis.Cluster('redis://user:pass@localhost:6379');
const KeyvValkey = new KeyvValkey(redis);
const keyv = new Keyv({ store: KeyvValkey });
```
## Options
# Options

### useRedisSets
## useSets

The `useRedisSets` option lets you decide whether to use Redis sets for key management. By default, this option is set to `true`.
The `useSets` option lets you decide whether to use Redis sets for key management. By default, this option is set to `true`.

When `useRedisSets` is enabled (`true`):
When `useSets` is enabled (`true`):

- A namespace for the Redis sets is created, and all created keys are added to this. This allows for group management of keys.
- When a key is deleted, it's removed not only from the main storage but also from the Redis set.
- When clearing all keys (using the `clear` function), all keys in the Redis set are looked up for deletion. The set itself is also deleted.

**Note**: In high-performance scenarios, enabling `useRedisSets` might lead to memory leaks. If you're running a high-performance application or service, it is recommended to set `useRedisSets` to `false`.
**Note**: In high-performance scenarios, enabling `useSets` might lead to memory leaks. If you're running a high-performance application or service, it is recommended to set `useSets` to `false`.

If you decide to set `useRedisSets` as `false`, keys will be handled individually and Redis sets won't be utilized.
If you decide to set `useSets` as `false`, keys will be handled individually and Redis sets won't be utilized.

However, please note that setting `useRedisSets` to `false` could lead to performance issues in production when using the `clear` function, as it will need to iterate over all keys to delete them.
However, please note that setting `useSets` to `false` could lead to performance issues in production when using the `clear` function, as it will need to iterate over all keys to delete them.

#### Example
## Example

Here's how you can use the `useRedisSets` option:
Here's how you can use the `useSets` option:

```js
import Keyv from 'keyv';

const keyv = new Keyv(new KeyvValkey('redis://user:pass@localhost:6379', { useRedisSets: false }));
const keyv = new Keyv(new KeyvValkey('redis://user:pass@localhost:6379', { useSets: false }));
```

## License
Expand Down
2 changes: 1 addition & 1 deletion packages/valkey/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
},
"devDependencies": {
"@keyv/test-suite": "*",
"keyv": "^5.0.3",
"keyv": "*",
"rimraf": "^6.0.1",
"timekeeper": "^2.3.1",
"tsd": "^0.31.2",
Expand Down
18 changes: 10 additions & 8 deletions packages/valkey/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import EventEmitter from 'events';
import Redis from 'iovalkey';
import {type KeyvStoreAdapter, type StoredData} from 'keyv';
import {Keyv, type KeyvStoreAdapter, type StoredData} from 'keyv';
import {
type KeyvValkeyOptions,
type KeyvUriOptions,
} from './types.js';

export const createKeyv = <Value>(uri: KeyvValkeyOptions | KeyvUriOptions, options?: KeyvValkeyOptions) => new Keyv<Value>({store: new KeyvValkey(uri, options)});

class KeyvValkey extends EventEmitter implements KeyvStoreAdapter {
ttlSupport = true;
namespace?: string;
Expand All @@ -14,7 +16,7 @@ class KeyvValkey extends EventEmitter implements KeyvStoreAdapter {
constructor(uri: KeyvValkeyOptions | KeyvUriOptions, options?: KeyvValkeyOptions) {
super();
this.opts = {};
this.opts.useRedisSets = true;
this.opts.useSets = true;
this.opts.dialect = 'redis';

if (typeof uri !== 'string' && uri.options && ('family' in uri.options || uri.isCluster)) {
Expand All @@ -25,8 +27,8 @@ class KeyvValkey extends EventEmitter implements KeyvStoreAdapter {
this.redis = new Redis(options.uri!, options);
}

if (options !== undefined && options.useRedisSets === false) {
this.opts.useRedisSets = false;
if (options !== undefined && options.useSets === false) {
this.opts.useSets = false;
}

this.redis.on('error', (error: Error) => this.emit('error', error));
Expand All @@ -37,7 +39,7 @@ class KeyvValkey extends EventEmitter implements KeyvStoreAdapter {
}

_getKeyName = (key: string): string => {
if (!this.opts.useRedisSets) {
if (!this.opts.useSets) {
return `sets:${this._getNamespace()}:${key}`;
}

Expand Down Expand Up @@ -75,7 +77,7 @@ class KeyvValkey extends EventEmitter implements KeyvStoreAdapter {
}
};

if (this.opts.useRedisSets) {
if (this.opts.useSets) {
const trx = await this.redis.multi();
await set(trx);
await trx.sadd(this._getNamespace(), key);
Expand All @@ -90,7 +92,7 @@ class KeyvValkey extends EventEmitter implements KeyvStoreAdapter {
let items = 0;
const unlink = async (redis: any) => redis.unlink(key);

if (this.opts.useRedisSets) {
if (this.opts.useSets) {
const trx = this.redis.multi();
await unlink(trx);
await trx.srem(this._getNamespace(), key);
Expand All @@ -111,7 +113,7 @@ class KeyvValkey extends EventEmitter implements KeyvStoreAdapter {
}

async clear() {
if (this.opts.useRedisSets) {
if (this.opts.useSets) {
const keys: string[] = await this.redis.smembers(this._getNamespace());
if (keys.length > 0) {
await Promise.all([
Expand Down
2 changes: 1 addition & 1 deletion packages/valkey/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export type KeyvValkeyOptions = {
} & {
uri?: string;
dialect?: string;
useRedisSets?: boolean;
useSets?: boolean;
};

export type KeyvUriOptions = string | KeyvValkeyOptions | Redis | Cluster;
31 changes: 19 additions & 12 deletions packages/valkey/test/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import tk from 'timekeeper';
import keyvTestSuite, {keyvIteratorTests} from '@keyv/test-suite';
import Keyv from 'keyv';
import Redis from 'iovalkey';
import KeyvValkey from '../src/index.js';
import KeyvValkey, {createKeyv} from '../src/index.js';

// eslint-disable-next-line @typescript-eslint/naming-convention
const REDIS_HOST = 'localhost:6370';
Expand Down Expand Up @@ -123,8 +123,8 @@ test.it('should handle KeyvOptions with family option', t => {
t.expect(keyv.redis instanceof Redis).toBeTruthy();
});

test.it('set method should use Redis sets when useRedisSets is false', async t => {
const options = {useRedisSets: false};
test.it('set method should use Redis sets when useSets is false', async t => {
const options = {useSets: false};
const keyv = new KeyvValkey(options);

await keyv.set('foo', 'bar');
Expand All @@ -133,8 +133,8 @@ test.it('set method should use Redis sets when useRedisSets is false', async t =
t.expect(value).toBe('bar');
});

test.it('clear method when useRedisSets is false', async t => {
const options = {useRedisSets: false};
test.it('clear method when useSets is false', async t => {
const options = {useSets: false};
const keyv = new KeyvValkey(options);

await keyv.set('foo', 'bar');
Expand All @@ -148,22 +148,22 @@ test.it('clear method when useRedisSets is false', async t => {
t.expect(value2).toBe(undefined);
});

test.it('clear method when useRedisSets is false and empty keys should not error', async t => {
const options = {useRedisSets: false};
test.it('clear method when useSets is false and empty keys should not error', async t => {
const options = {useSets: false};
const keyv = new KeyvValkey(options);
t.expect(await keyv.clear()).toBeUndefined();
});

test.it('when passing in ioredis set the options.useRedisSets', t => {
const options = {useRedisSets: false};
test.it('when passing in ioredis set the options.useSets', t => {
const options = {useSets: false};
const redis = new Redis(redisURI);
const keyv = new KeyvValkey(redis, options);

t.expect(keyv.opts.useRedisSets).toBe(false);
t.expect(keyv.opts.useSets).toBe(false);
});

test.it('del should work when not using useRedisSets', async t => {
const options = {useRedisSets: false};
test.it('del should work when not using useSets', async t => {
const options = {useSets: false};
const redis = new Redis(redisURI);
const keyv = new KeyvValkey(redis, options);

Expand All @@ -175,3 +175,10 @@ test.it('del should work when not using useRedisSets', async t => {

t.expect(value).toBe(undefined);
});

test.it('can create a full keyv instance with a uri', async t => {
const keyv = createKeyv(redisURI);
t.expect(keyv).toBeTruthy();
await keyv.set('foo222', 'bar222');
t.expect(await keyv.get('foo222')).toBe('bar222');
});

0 comments on commit 835e6cb

Please sign in to comment.