1
0
mirror of https://github.com/redis/node-redis.git synced 2025-08-06 02:15:48 +03:00
This commit is contained in:
Leibale
2023-06-07 11:45:48 -04:00
parent 05f9f0ee0d
commit cf79a806c5
22 changed files with 1761 additions and 7268 deletions

View File

@@ -27,7 +27,7 @@
## Map keys and Set members
When decoding Map to `Map | object` or Set to `Set`, keys/members (respectively) of type "Simple String" or "Blob String" will be decoded as `string`s (ignoring flags) to allow lookup by type. If you need them as `Buffer`s, make sure to decode `Map`s/`Set`s as `Array`s.
When decoding Map to `Map | object` or Set to `Set`, keys/members (respectively) of type "Simple String" or "Blob String" will be decoded as `string`s (ignoring type mapping) to allow lookup by type. If you need them as `Buffer`s, make sure to decode `Map`s/`Set`s as `Array`s.
## Not Implemented

View File

@@ -13,11 +13,11 @@ await client.connect();
client.hGetAll('key'); // Record<string, string>
client.withFlags({
client.withTypeMapping({
[TYPES.MAP]: Map
}).hGetAll('key'); // Map<string, string>
client.withFlags({
client.withTypeMapping({
[TYPES.MAP]: Map,
[TYPES.BLOB_STRING]: Buffer
}).hGetAll('key'); // Map<string, Buffer>

5674
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -6,16 +6,22 @@
],
"scripts": {
"test": "npm run test -ws --if-present",
"build:client": "npm run build -w ./packages/client",
"build:test-utils": "npm run build -w ./packages/test-utils",
"build:modules": "npm run build -w ./packages/bloom -w ./packages/graph -w ./packages/json -w ./packages/search -w ./packages/time-series",
"build:redis": "npm run build -w ./packages/redis",
"build": "npm run build:client && npm run build:test-utils && npm run build:modules && npm run build:redis",
"documentation": "npm run documentation -ws --if-present",
"build": "tsc --build",
"documentation": "typedoc",
"gh-pages": "gh-pages -d ./documentation -e ./documentation -u 'documentation-bot <documentation@bot>'"
},
"devDependencies": {
"@tsconfig/node16": "^1.0.3",
"gh-pages": "^5.0.0"
"@istanbuljs/nyc-config-typescript": "^1.0.2",
"@tsconfig/node16": "^1.0.4",
"@types/mocha": "^10.0.1",
"@typescript-eslint/eslint-plugin": "^5.59.7",
"@typescript-eslint/parser": "^5.59.7",
"gh-pages": "^5.0.0",
"mocha": "^10.2.0",
"nyc": "^15.1.0",
"source-map-support": "^0.5.21",
"ts-node": "^10.9.1",
"typedoc": "^0.24.7",
"typescript": "^5.0.4"
}
}

View File

@@ -8,23 +8,13 @@
"dist/"
],
"scripts": {
"test": "nyc -r text-summary -r lcov mocha -r source-map-support/register -r ts-node/register './lib/**/*.spec.ts'",
"build": "tsc",
"documentation": "typedoc"
"test": "nyc -r text-summary -r lcov mocha -r source-map-support/register -r ts-node/register './lib/**/*.spec.ts'"
},
"peerDependencies": {
"@redis/client": "^1.0.0"
"@redis/client": "*"
},
"devDependencies": {
"@istanbuljs/nyc-config-typescript": "^1.0.2",
"@redis/test-utils": "*",
"@types/node": "^18.16.1",
"nyc": "^15.1.0",
"release-it": "^15.10.1",
"source-map-support": "^0.5.21",
"ts-node": "^10.9.1",
"typedoc": "^0.24.6",
"typescript": "^5.0.4"
"@redis/test-utils": "*"
},
"repository": {
"type": "git",

View File

@@ -1,15 +0,0 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
"rules": {
"semi": [2, "always"]
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,146 +1,117 @@
// import { strict as assert } from 'assert';
// import testUtils, { GLOBAL, waitTillBeenCalled } from '../test-utils';
// import RedisClient, { RedisClientType } from '.';
import { strict as assert } from 'assert';
import testUtils, { GLOBAL, waitTillBeenCalled } from '../test-utils';
import RedisClient, { RedisClientType } from '.';
// import { RedisClientMultiCommandType } from './multi-command';
// import { RedisCommandRawReply, RedisModules, RedisFunctions, RedisScripts } from '../commands';
// import { AbortError, ClientClosedError, ClientOfflineError, ConnectionTimeoutError, DisconnectsClientError, SocketClosedUnexpectedlyError, WatchError } from '../errors';
// import { defineScript } from '../lua-script';
import { defineScript } from '../lua-script';
// import { spy } from 'sinon';
// import { once } from 'events';
// import { ClientKillFilters } from '../commands/CLIENT_KILL';
// import { promisify } from 'util';
// export const SQUARE_SCRIPT = defineScript({
// SCRIPT: 'return ARGV[1] * ARGV[1];',
// NUMBER_OF_KEYS: 0,
// transformArguments(number: number): Array<string> {
// return [number.toString()];
// }
// });
export const SQUARE_SCRIPT = defineScript({
SCRIPT: 'return ARGV[1] * ARGV[1];',
NUMBER_OF_KEYS: 0,
transformArguments(number: number): Array<string> {
return [number.toString()];
},
transformReply: undefined as unknown as () => number
});
// export const MATH_FUNCTION = {
// name: 'math',
// engine: 'LUA',
// code: `#!LUA name=math
// redis.register_function{
// function_name = "square",
// callback = function(keys, args) return args[1] * args[1] end,
// flags = { "no-writes" }
// }`,
// library: {
// square: {
// NAME: 'square',
// IS_READ_ONLY: true,
// NUMBER_OF_KEYS: 0,
// transformArguments(number: number): Array<string> {
// return [number.toString()];
// }
// }
// }
// };
describe('Client', () => {
describe('parseURL', () => {
it('redis://user:secret@localhost:6379/0', () => {
assert.deepEqual(
RedisClient.parseURL('redis://user:secret@localhost:6379/0'),
{
socket: {
host: 'localhost',
port: 6379
},
username: 'user',
password: 'secret',
database: 0
}
);
});
// export async function loadMathFunction(
// client: RedisClientType<RedisModules, RedisFunctions, RedisScripts>
// ): Promise<void> {
// await client.functionLoad(
// MATH_FUNCTION.code,
// { REPLACE: true }
// );
// }
it('rediss://user:secret@localhost:6379/0', () => {
assert.deepEqual(
RedisClient.parseURL('rediss://user:secret@localhost:6379/0'),
{
socket: {
host: 'localhost',
port: 6379,
tls: true
},
username: 'user',
password: 'secret',
database: 0
}
);
});
// describe('Client', () => {
// describe('parseURL', () => {
// it('redis://user:secret@localhost:6379/0', () => {
// assert.deepEqual(
// RedisClient.parseURL('redis://user:secret@localhost:6379/0'),
// {
// socket: {
// host: 'localhost',
// port: 6379
// },
// username: 'user',
// password: 'secret',
// database: 0
// }
// );
// });
it('Invalid protocol', () => {
assert.throws(
() => RedisClient.parseURL('redi://user:secret@localhost:6379/0'),
TypeError
);
});
// it('rediss://user:secret@localhost:6379/0', () => {
// assert.deepEqual(
// RedisClient.parseURL('rediss://user:secret@localhost:6379/0'),
// {
// socket: {
// host: 'localhost',
// port: 6379,
// tls: true
// },
// username: 'user',
// password: 'secret',
// database: 0
// }
// );
// });
it('Invalid pathname', () => {
assert.throws(
() => RedisClient.parseURL('redis://user:secret@localhost:6379/NaN'),
TypeError
);
});
// it('Invalid protocol', () => {
// assert.throws(
// () => RedisClient.parseURL('redi://user:secret@localhost:6379/0'),
// TypeError
// );
// });
it('redis://localhost', () => {
assert.deepEqual(
RedisClient.parseURL('redis://localhost'),
{
socket: {
host: 'localhost',
}
}
);
});
});
// it('Invalid pathname', () => {
// assert.throws(
// () => RedisClient.parseURL('redis://user:secret@localhost:6379/NaN'),
// TypeError
// );
// });
describe('authentication', () => {
testUtils.testWithClient('Client should be authenticated', async client => {
assert.equal(
await client.ping(),
'PONG'
);
}, GLOBAL.SERVERS.PASSWORD);
// it('redis://localhost', () => {
// assert.deepEqual(
// RedisClient.parseURL('redis://localhost'),
// {
// socket: {
// host: 'localhost',
// }
// }
// );
// });
// });
testUtils.testWithClient('should execute AUTH before SELECT', async client => {
assert.equal(
(await client.clientInfo()).db,
2
);
}, {
...GLOBAL.SERVERS.PASSWORD,
clientOptions: {
...GLOBAL.SERVERS.PASSWORD.clientOptions,
database: 2
},
minimumDockerVersion: [6, 2]
});
});
// describe('authentication', () => {
// testUtils.testWithClient('Client should be authenticated', async client => {
// assert.equal(
// await client.ping(),
// 'PONG'
// );
// }, GLOBAL.SERVERS.PASSWORD);
// testUtils.testWithClient('should execute AUTH before SELECT', async client => {
// assert.equal(
// (await client.clientInfo()).db,
// 2
// );
// }, {
// ...GLOBAL.SERVERS.PASSWORD,
// clientOptions: {
// ...GLOBAL.SERVERS.PASSWORD.clientOptions,
// database: 2
// },
// minimumDockerVersion: [6, 2]
// });
// });
// testUtils.testWithClient('should set connection name', async client => {
// assert.equal(
// await client.clientGetName(),
// 'name'
// );
// }, {
// ...GLOBAL.SERVERS.OPEN,
// clientOptions: {
// name: 'name'
// }
// });
testUtils.testWithClient('should set connection name', async client => {
assert.equal(
await client.clientGetName(),
'name'
);
}, {
...GLOBAL.SERVERS.OPEN,
clientOptions: {
name: 'name'
}
});
// describe('legacyMode', () => {
// testUtils.testWithClient('client.sendCommand should call the callback', async client => {
@@ -770,8 +741,8 @@
// }
// function assertBufferListener(message: Buffer, channel: Buffer) {
// assert.ok(Buffer.isBuffer(message));
// assert.ok(Buffer.isBuffer(channel));
// assert.ok(message instanceof Buffer);
// assert.ok(channel instanceof Buffer);
// }
// const subscriber = publisher.duplicate();
@@ -978,4 +949,4 @@
// },
// disableClientSetup: true
// });
// });
});

View File

@@ -586,7 +586,7 @@ export default class RedisClusterSlots<
await unsubscribe(client);
if (!client.isPubSubActive) {
await client.disconnect();
client.destroy();
this.pubSubNode = undefined;
}
}

View File

@@ -1,5 +1,5 @@
import { ClientCommandOptions, RedisClientOptions } from '../client';
import { Command, CommandArguments, CommanderConfig, CommandPolicies, CommandWithPoliciesSignature, Flags, RedisArgument, RedisFunction, RedisFunctions, RedisModules, RedisScript, RedisScripts, ReplyUnion, RespVersions } from '../RESP/types';
import { Command, CommandArguments, CommanderConfig, CommandPolicies, CommandWithPoliciesSignature, TypeMapping, RedisArgument, RedisFunction, RedisFunctions, RedisModules, RedisScript, RedisScripts, ReplyUnion, RespVersions } from '../RESP/types';
import COMMANDS from '../commands';
import { EventEmitter } from 'events';
import { attachConfig, functionArgumentsPrefix, getTransformReply, scriptArgumentsPrefix } from '../commander';
@@ -53,10 +53,10 @@ export interface RedisClusterOptions<
type WithCommands<
RESP extends RespVersions,
FLAGS extends Flags,
TYPE_MAPPING extends TypeMapping,
POLICIES extends CommandPolicies
> = {
[P in keyof typeof COMMANDS]: CommandWithPoliciesSignature<(typeof COMMANDS)[P], RESP, FLAGS, POLICIES>;
[P in keyof typeof COMMANDS]: CommandWithPoliciesSignature<(typeof COMMANDS)[P], RESP, TYPE_MAPPING, POLICIES>;
};
export type RedisClusterType<
@@ -64,16 +64,16 @@ export type RedisClusterType<
F extends RedisFunctions = {},
S extends RedisScripts = {},
RESP extends RespVersions = 2,
FLAGS extends Flags = {},
TYPE_MAPPING extends TypeMapping = {},
POLICIES extends CommandPolicies = {}
> = RedisCluster<M, F, S, RESP, FLAGS, POLICIES> & WithCommands<RESP, FLAGS, POLICIES>;
> = RedisCluster<M, F, S, RESP, TYPE_MAPPING, POLICIES> & WithCommands<RESP, TYPE_MAPPING, POLICIES>;
// & WithModules<M> & WithFunctions<F> & WithScripts<S>
export interface ClusterCommandOptions extends ClientCommandOptions {
policies?: CommandPolicies;
}
type ProxyCluster = RedisCluster<RedisModules, RedisFunctions, RedisScripts, RespVersions, Flags, CommandPolicies> & { commandOptions?: ClusterCommandOptions };
type ProxyCluster = RedisCluster<RedisModules, RedisFunctions, RedisScripts, RespVersions, TypeMapping, CommandPolicies> & { commandOptions?: ClusterCommandOptions };
type NamespaceProxyCluster = { self: ProxyCluster };
@@ -82,7 +82,7 @@ export default class RedisCluster<
F extends RedisFunctions,
S extends RedisScripts,
RESP extends RespVersions,
FLAGS extends Flags,
TYPE_MAPPING extends TypeMapping,
POLICIES extends CommandPolicies
> extends EventEmitter {
static extractFirstKey<C extends Command>(
@@ -310,7 +310,7 @@ export default class RedisCluster<
F,
S,
RESP,
T['flags'] extends Flags ? T['flags'] : {},
T['typeMapping'] extends TypeMapping ? T['typeMapping'] : {},
T['policies'] extends CommandPolicies ? T['policies'] : {}
>;
}
@@ -330,16 +330,16 @@ export default class RedisCluster<
F,
S,
RESP,
K extends 'flags' ? V extends Flags ? V : {} : FLAGS,
K extends 'typeMapping' ? V extends TypeMapping ? V : {} : TYPE_MAPPING,
K extends 'policies' ? V extends CommandPolicies ? V : {} : POLICIES
>;
}
/**
* Override the `flags` command option
* Override the `typeMapping` command option
*/
withFlags<FLAGS extends Flags>(flags: FLAGS) {
return this._commandOptionsProxy('flags', flags);
withTypeMapping<TYPE_MAPPING extends TypeMapping>(typeMapping: TYPE_MAPPING) {
return this._commandOptionsProxy('typeMapping', typeMapping);
}
/**
@@ -421,7 +421,7 @@ export default class RedisCluster<
return client.executeMulti(commands);
}
MULTI(routing?: RedisArgument): RedisClusterMultiCommandType<[], M, F, S, RESP, FLAGS> {
MULTI(routing?: RedisArgument): RedisClusterMultiCommandType<[], M, F, S, RESP, TYPE_MAPPING> {
return new (this as any).Multi(
this,
routing

View File

@@ -8,35 +8,19 @@
"dist/"
],
"scripts": {
"test": "nyc -r text-summary -r lcov mocha -r source-map-support/register -r ts-node/register './lib/**/*.spec.ts'",
"build": "tsc",
"lint": "eslint ./*.ts ./lib/**/*.ts",
"documentation": "typedoc"
"test": "nyc -r text-summary -r lcov mocha -r source-map-support/register -r ts-node/register './lib/**/*.spec.ts'"
},
"dependencies": {
"cluster-key-slot": "1.1.2",
"generic-pool": "3.9.0",
"yallist": "4.0.0"
"generic-pool": "3.9.0"
},
"devDependencies": {
"@istanbuljs/nyc-config-typescript": "^1.0.2",
"@redis/test-utils": "*",
"@types/node": "^18.16.1",
"@types/sinon": "^10.0.14",
"@types/yallist": "^4.0.1",
"@typescript-eslint/eslint-plugin": "^5.59.1",
"@typescript-eslint/parser": "^5.59.1",
"eslint": "^8.39.0",
"nyc": "^15.1.0",
"release-it": "^15.10.1",
"sinon": "^15.0.4",
"source-map-support": "^0.5.21",
"ts-node": "^10.9.1",
"typedoc": "^0.24.6",
"typescript": "^5.0.4"
"@types/sinon": "^10.0.15",
"sinon": "^15.1.0"
},
"engines": {
"node": ">=14"
"node": ">=16"
},
"repository": {
"type": "git",

View File

@@ -8,23 +8,12 @@
"dist/"
],
"scripts": {
"test": "nyc -r text-summary -r lcov mocha -r source-map-support/register -r ts-node/register './lib/**/*.spec.ts'",
"build": "tsc",
"documentation": "typedoc"
},
"test": "nyc -r text-summary -r lcov mocha -r source-map-support/register -r ts-node/register './lib/**/*.spec.ts'" },
"peerDependencies": {
"@redis/client": "^1.0.0"
"@redis/client": "*"
},
"devDependencies": {
"@istanbuljs/nyc-config-typescript": "^1.0.2",
"@redis/test-utils": "*",
"@types/node": "^18.16.1",
"nyc": "^15.1.0",
"release-it": "^15.10.1",
"source-map-support": "^0.5.21",
"ts-node": "^10.9.1",
"typedoc": "^0.24.6",
"typescript": "^5.0.4"
"@redis/test-utils": "*"
},
"repository": {
"type": "git",

View File

@@ -8,23 +8,13 @@
"dist/"
],
"scripts": {
"test": "nyc -r text-summary -r lcov mocha -r source-map-support/register -r ts-node/register './lib/**/*.spec.ts'",
"build": "tsc",
"documentation": "typedoc"
"test": "nyc -r text-summary -r lcov mocha -r source-map-support/register -r ts-node/register './lib/**/*.spec.ts'"
},
"peerDependencies": {
"@redis/client": "^1.0.0"
"@redis/client": "*"
},
"devDependencies": {
"@istanbuljs/nyc-config-typescript": "^1.0.2",
"@redis/test-utils": "*",
"@types/node": "^18.16.1",
"nyc": "^15.1.0",
"release-it": "^15.10.1",
"source-map-support": "^0.5.21",
"ts-node": "^10.9.1",
"typedoc": "^0.24.6",
"typescript": "^5.0.4"
"@redis/test-utils": "*"
},
"repository": {
"type": "git",

View File

@@ -25,15 +25,13 @@ To install node-redis, simply:
npm install redis
```
> :warning: The new interface is clean and cool, but if you have an existing codebase, you'll want to read the [migration guide](./docs/v3-to-v4.md).
Looking for a high-level library to handle object mapping? See [redis-om-node](https://github.com/redis/redis-om-node)!
## Usage
### Basic Example
```typescript
```javascript
import { createClient } from 'redis';
const client = createClient();
@@ -44,18 +42,18 @@ await client.connect();
await client.set('key', 'value');
const value = await client.get('key');
await client.disconnect();
client.destroy();
```
The above code connects to localhost on port 6379. To connect to a different host or port, use a connection string in the format `redis[s]://[[username][:password]@][host][:port][/db-number]`:
```typescript
```javascript
createClient({
url: 'redis://alice:foobared@awesome.redis.server:6380'
});
```
You can also use discrete parameters, UNIX sockets, and even TLS to connect. Details can be found in the [client configuration guide](./docs/client-configuration.md).
You can also use discrete parameters, UNIX sockets, and even TLS to connect. Details can be found in the [client configuration guide](../../docs/client-configuration.md).
To check if the the client is connected and ready to send commands, use `client.isReady` which returns a boolean. `client.isOpen` is also available. This returns `true` when the client's underlying socket is open, and `false` when it isn't (for example when the client is still connecting or reconnecting after a network error).
@@ -96,7 +94,7 @@ await client.hVals('key'); // ['value1', 'value2']
```typescript
await client.hSet('key', 'field', Buffer.from('value')); // 'OK'
await client.withFlags({
await client.withTypeMapping({
[TYPES.BLOB_STRING]: Buffer
}).hGetAll('key'); // { field: <Buffer 76 61 6c 75 65> }
```
@@ -111,23 +109,11 @@ await client.sendCommand(['SET', 'key', 'value', 'NX']); // 'OK'
await client.sendCommand(['HGETALL', 'key']); // ['key1', 'field1', 'key2', 'field2']
```
### Transactions (Multi/Exec)
Start a [transaction](https://redis.io/topics/transactions) by calling `.multi()`, then chaining your commands. When you're done, call `.exec()` and you'll get an array back with your results:
```typescript
await client.set('another-key', 'another-value');
const [setKeyReply, otherKeyValue] = await client
.multi()
.set('key', 'value')
.get('another-key')
.exec(); // ['OK', 'another-value']
```
You can also [watch](https://redis.io/topics/transactions#optimistic-locking-using-check-and-set) keys by calling `.watch()`. Your transaction will abort if any of the watched keys change.
To dig deeper into transactions, check out the [Isolated Execution Guide](./docs/isolated-execution.md).
### Links
- [Multi](../../docs/multi.md).
- [Pub/Sub](../../docs/pub-sub.md).
- [Scan Iterators](../../docs/scan-iterators.md).
- [Programmability](../../docs/programmability.md).
### Blocking Commands
@@ -148,150 +134,33 @@ await client.lPush('key', ['1', '2']);
await blPopPromise; // '2'
```
To learn more about isolated execution, check out the [guide](./docs/isolated-execution.md).
### Pub/Sub
See the [Pub/Sub overview](./docs/pub-sub.md).
### Scan Iterator
[`SCAN`](https://redis.io/commands/scan) results can be looped over using [async iterators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/asyncIterator):
```typescript
for await (const key of client.scanIterator()) {
// use the key!
await client.get(key);
}
```
This works with `HSCAN`, `SSCAN`, and `ZSCAN` too:
```typescript
for await (const { field, value } of client.hScanIterator('hash')) {}
for await (const member of client.sScanIterator('set')) {}
for await (const { score, value } of client.zScanIterator('sorted-set')) {}
```
You can override the default options by providing a configuration object:
```typescript
client.scanIterator({
TYPE: 'string', // `SCAN` only
MATCH: 'patter*',
COUNT: 100
});
```
### [Programmability](https://redis.io/docs/manual/programmability/)
Redis provides a programming interface allowing code execution on the redis server.
#### [Functions](https://redis.io/docs/manual/programmability/functions-intro/)
The following example retrieves a key in redis, returning the value of the key, incremented by an integer. For example, if your key _foo_ has the value _17_ and we run `add('foo', 25)`, it returns the answer to Life, the Universe and Everything.
```lua
#!lua name=library
redis.register_function {
function_name = 'add',
callback = function(keys, args) return redis.call('GET', keys[1]) + args[1] end,
flags = { 'no-writes' }
}
```
Here is the same example, but in a format that can be pasted into the `redis-cli`.
```
FUNCTION LOAD "#!lua name=library\nredis.register_function{function_name=\"add\", callback=function(keys, args) return redis.call('GET', keys[1])+args[1] end, flags={\"no-writes\"}}"
```
Load the prior redis function on the _redis server_ before running the example below.
```typescript
import { createClient } from 'redis';
const client = createClient({
functions: {
library: {
add: {
NUMBER_OF_KEYS: 1,
transformArguments(key: string, toAdd: number): Array<string> {
return [key, toAdd.toString()];
},
transformReply(reply: number): number {
return reply;
}
}
}
}
});
await client.connect();
await client.set('key', '1');
await client.library.add('key', 2); // 3
```
#### [Lua Scripts](https://redis.io/docs/manual/programmability/eval-intro/)
The following is an end-to-end example of the prior concept.
```typescript
import { createClient, defineScript } from 'redis';
const client = createClient({
scripts: {
add: defineScript({
NUMBER_OF_KEYS: 1,
SCRIPT:
'return redis.call("GET", KEYS[1]) + ARGV[1];',
transformArguments(key: string, toAdd: number): Array<string> {
return [key, toAdd.toString()];
},
transformReply(reply: number): number {
return reply;
}
})
}
});
await client.connect();
await client.set('key', '1');
await client.add('key', 2); // 3
```
To learn more about isolated execution, check out the [guide](../../docs/isolated-execution.md).
### Disconnecting
There are two functions that disconnect a client from the Redis server. In most scenarios you should use `.quit()` to ensure that pending commands are sent to Redis before closing a connection.
There are two functions that disconnect a client from the Redis server. In most scenarios you should use `.close()` to ensure that pending commands are sent to Redis before closing a connection.
#### `.QUIT()`/`.quit()`
Gracefully close a client's connection to Redis, by sending the [`QUIT`](https://redis.io/commands/quit) command to the server. Before quitting, the client executes any remaining commands in its queue, and will receive replies from Redis for each of them.
#### `.close()`
```typescript
const [ping, get, quit] = await Promise.all([
const [ping, get] = await Promise.all([
client.ping(),
client.get('key'),
client.quit()
]); // ['PONG', null, 'OK']
client.close()
]); // ['PONG', null]
try {
await client.get('key');
} catch (err) {
// ClosedClient Error
// ClientClosedError
}
```
#### `.disconnect()`
> :warning: `.close` is just like `.quit()` which was depreacted in Redis 7.2. See the [relevant section in the migration guide](../../docs/v4-to-v5.md#Quit-VS-Disconnect) for more information.
Forcibly close a client's connection to Redis immediately. Calling `disconnect` will not send further pending commands to the Redis server, or wait for or parse outstanding responses.
#### `.destroy()`
```typescript
await client.disconnect();
```
Forcibly close a client's connection to Redis immediately. Calling `destroy` will not send further pending commands to the Redis server, or wait for or parse outstanding responses.
### Auto-Pipelining
@@ -311,9 +180,25 @@ await Promise.all([
]);
```
### Aborting Commands
If you want to abort a command, you can use the `AbortController` API:
```typescript
const controller = new AbortController();
client.withAbortSignal(contoller.signal).get('key').catch(err => {
// AbortError
});
controller.abort();
```
> :watning: commands can only be aborted before they are sent to Redis. Once a command is sent (written on the socket), it cannot be aborted.
### Clustering
Check out the [Clustering Guide](./docs/clustering.md) when using Node Redis to connect to a Redis Cluster.
Check out the [Clustering Guide](../../docs/clustering.md) when using Node Redis to connect to a Redis Cluster.
### Events
@@ -326,11 +211,11 @@ The Node Redis client class is an Nodejs EventEmitter and it emits an event each
| `end` | Connection has been closed (via `.quit()` or `.disconnect()`) | *No arguments* |
| `error` | An error has occurred—usually a network issue such as "Socket closed unexpectedly" | `(error: Error)` |
| `reconnecting` | Client is trying to reconnect to the server | *No arguments* |
| `sharded-channel-moved` | See [here](./docs/pub-sub.md#sharded-channel-moved-event) | See [here](./docs/pub-sub.md#sharded-channel-moved-event) |
| `sharded-channel-moved` | See [here](../../docs/pub-sub.md#sharded-channel-moved-event) | See [here](../../docs/pub-sub.md#sharded-channel-moved-event) |
> :warning: You **MUST** listen to `error` events. If a client doesn't have at least one `error` listener registered and an `error` occurs, that error will be thrown and the Node.js process will exit. See the [`EventEmitter` docs](https://nodejs.org/api/events.html#events_error_events) for more details.
> The client will not emit [any other events](./docs/v3-to-v4.md#all-the-removed-events) beyond those listed above.
> The client will not emit [any other events](../../docs/v3-to-v4.md#all-the-removed-events) beyond those listed above.
## Supported Redis versions
@@ -348,7 +233,7 @@ Node Redis is supported with the following versions of Redis:
## Contributing
If you'd like to contribute, check out the [contributing guide](CONTRIBUTING.md).
If you'd like to contribute, check out the [contributing guide](../../CONTRIBUTING.md).
Thank you to all the people who already contributed to Node Redis!
@@ -356,4 +241,4 @@ Thank you to all the people who already contributed to Node Redis!
## License
This repository is licensed under the "MIT" license. See [LICENSE](LICENSE).
This repository is licensed under the "MIT" license. See [LICENSE](../../LICENSE).

View File

@@ -8,9 +8,6 @@
"files": [
"dist/"
],
"scripts": {
"build": "tsc"
},
"dependencies": {
"@redis/bloom": "1.2.0",
"@redis/client": "1.5.7",
@@ -19,10 +16,6 @@
"@redis/search": "1.1.2",
"@redis/time-series": "1.0.4"
},
"devDependencies": {
"release-it": "^15.9.3",
"typescript": "^5.0.2"
},
"repository": {
"type": "git",
"url": "git://github.com/redis/node-redis.git"

View File

@@ -8,23 +8,13 @@
"dist/"
],
"scripts": {
"test": "nyc -r text-summary -r lcov mocha -r source-map-support/register -r ts-node/register './lib/**/*.spec.ts'",
"build": "tsc",
"documentation": "typedoc"
"test": "nyc -r text-summary -r lcov mocha -r source-map-support/register -r ts-node/register './lib/**/*.spec.ts'"
},
"peerDependencies": {
"@redis/client": "^1.0.0"
"@redis/client": "*"
},
"devDependencies": {
"@istanbuljs/nyc-config-typescript": "^1.0.2",
"@redis/test-utils": "*",
"@types/node": "^18.16.1",
"nyc": "^15.1.0",
"release-it": "^15.10.1",
"source-map-support": "^0.5.21",
"ts-node": "^10.9.1",
"typedoc": "^0.24.6",
"typescript": "^5.0.4"
"@redis/test-utils": "*"
},
"repository": {
"type": "git",

View File

@@ -1,7 +1,7 @@
import { createConnection } from 'net';
import { once } from 'events';
import RedisClient from '@redis/client/dist/lib/client';
import { promiseTimeout } from '@redis/client/dist/lib/utils';
import { createClient } from '@redis/client';
import { setTimeout } from 'timers/promises';
// import { ClusterSlotsReply } from '@redis/client/dist/lib/commands/CLUSTER_SLOTS';
import * as path from 'path';
import { promisify } from 'util';
@@ -46,8 +46,8 @@ export interface RedisServerDocker {
dockerId: string;
}
// ".." cause it'll be in `./dist`
const DOCKER_FODLER_PATH = path.join(__dirname, '../docker');
// extrea ".." cause it'll be in `./dist`
const DOCKER_FODLER_PATH = path.join(__dirname, '../../docker');
async function spawnRedisServerDocker({ image, version }: RedisServerDockerConfig, serverArguments: Array<string>): Promise<RedisServerDocker> {
const port = (await portIterator.next()).value,
@@ -64,7 +64,7 @@ async function spawnRedisServerDocker({ image, version }: RedisServerDockerConfi
}
while (await isPortAvailable(port)) {
await promiseTimeout(50);
await setTimeout(50);
}
return {
@@ -139,7 +139,7 @@ async function spawnRedisClusterNodeDockers(
await replica.client.clusterMeet('127.0.0.1', master.docker.port);
while ((await replica.client.clusterSlots()).length === 0) {
await promiseTimeout(50);
await setTimeout(50);
}
await replica.client.clusterReplicate(
@@ -168,7 +168,7 @@ async function spawnRedisClusterNodeDocker(
'--cluster-node-timeout',
'5000'
]),
client = RedisClient.create({
client = createClient({
socket: {
port: docker.port
}
@@ -220,10 +220,10 @@ async function spawnRedisClusterDockers(
totalNodes(await client.clusterSlots()) !== nodes.length ||
!(await client.sendCommand<string>(['CLUSTER', 'INFO'])).startsWith('cluster_state:ok') // TODO
) {
await promiseTimeout(50);
await setTimeout(50);
}
return client.disconnect();
client.destroy();
})
);

View File

@@ -1,7 +1,3 @@
// import { RedisModules, RedisFunctions, RedisScripts } from '@redis/client/lib/commands';
// import RedisClient, { RedisClientOptions, RedisClientType } from '@redis/client/lib/client';
// import RedisCluster, { RedisClusterOptions, RedisClusterType } from '@redis/client/lib/cluster';
// import { RedisSocketCommonOptions } from '@redis/client/lib/client/socket';
import {
RedisModules,
RedisFunctions,
@@ -13,7 +9,7 @@ import {
createCluster,
RedisClusterOptions,
RedisClusterType
} from '@redis/client/index';
} from '@redis/client';
import { RedisServerDockerConfig, spawnRedisServer, spawnRedisCluster } from './dockers';
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
@@ -136,10 +132,10 @@ export default class TestUtils {
}
testWithClient<
M extends RedisModules,
F extends RedisFunctions,
S extends RedisScripts,
RESP extends RespVersions
M extends RedisModules = {},
F extends RedisFunctions = {},
S extends RedisScripts = {},
RESP extends RespVersions = 2
>(
title: string,
fn: (client: RedisClientType<M, F, S, RESP>) => unknown,
@@ -181,7 +177,7 @@ export default class TestUtils {
} finally {
if (client.isOpen) {
await client.flushAll();
await client.disconnect();
client.destroy();
}
}
});
@@ -203,10 +199,10 @@ export default class TestUtils {
}
testWithCluster<
M extends RedisModules,
F extends RedisFunctions,
S extends RedisScripts,
RESP extends RespVersions
M extends RedisModules = {},
F extends RedisFunctions = {},
S extends RedisScripts = {},
RESP extends RespVersions = 2
>(
title: string,
fn: (cluster: RedisClusterType<M, F, S, RESP>) => unknown,
@@ -254,10 +250,10 @@ export default class TestUtils {
}
testAll<
M extends RedisModules,
F extends RedisFunctions,
S extends RedisScripts,
RESP extends RespVersions
M extends RedisModules = {},
F extends RedisFunctions = {},
S extends RedisScripts = {},
RESP extends RespVersions = 2
>(
title: string,
fn: (client: RedisClientType<M, F, S, RESP> | RedisClusterType<M, F, S, RESP>) => unknown,

View File

@@ -1,24 +1,13 @@
{
"name": "@redis/test-utils",
"private": true,
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"scripts": {
"build": "tsc"
},
"main": "./dist/lib/index.js",
"types": "./dist/lib/index.d.ts",
"peerDependencies": {
"@redis/client": "^1.0.0"
"@redis/client": "*"
},
"devDependencies": {
"@istanbuljs/nyc-config-typescript": "^1.0.2",
"@types/mocha": "^10.0.1",
"@types/node": "^18.16.1",
"@types/yargs": "^17.0.24",
"mocha": "^10.2.0",
"nyc": "^15.1.0",
"source-map-support": "^0.5.21",
"ts-node": "^10.9.1",
"typescript": "^5.0.4",
"yargs": "^17.7.1"
"yargs": "^17.7.2"
}
}

View File

@@ -8,23 +8,13 @@
"dist/"
],
"scripts": {
"test": "nyc -r text-summary -r lcov mocha -r source-map-support/register -r ts-node/register './lib/**/*.spec.ts'",
"build": "tsc",
"documentation": "typedoc"
"test": "nyc -r text-summary -r lcov mocha -r source-map-support/register -r ts-node/register './lib/**/*.spec.ts'"
},
"peerDependencies": {
"@redis/client": "^1.0.0"
"@redis/client": "*"
},
"devDependencies": {
"@istanbuljs/nyc-config-typescript": "^1.0.2",
"@redis/test-utils": "*",
"@types/node": "^18.16.1",
"nyc": "^15.1.0",
"release-it": "^15.10.1",
"source-map-support": "^0.5.21",
"ts-node": "^10.9.1",
"typedoc": "^0.24.6",
"typescript": "^5.0.4"
"@redis/test-utils": "*"
},
"repository": {
"type": "git",

View File

@@ -1,10 +1,9 @@
{
"extends": "@tsconfig/node16/tsconfig.json",
"compilerOptions": {
"composite": true,
"declaration": true,
"allowJs": true,
"useDefineForClassFields": true,
"esModuleInterop": false
"allowJs": true
},
"ts-node": {
"files": true

25
tsconfig.json Normal file
View File

@@ -0,0 +1,25 @@
{
"references": [{
"path": "./packages/client"
}, {
"path": "./packages/test-utils"
}],
"todo": [{
"path": "./packages/bloom"
}, {
"path": "./packages/graph"
}, {
"path": "./packages/json"
}, {
"path": "./packages/search"
}, {
"path": "./packages/time-series"
}, {
"path": "./packages/redis"
}],
"typedocOptions": {
"entryPoints": ["./packages/client"],
"entryPointStrategy": "packages",
"out": "./documentation"
}
}