1
0
mirror of https://github.com/redis/node-redis.git synced 2025-08-09 00:22:08 +03:00

update docs, add 6.0.x to the tests matrix, add eslint, npm update, fix some commands, fix some types

Co-authored-by: Simon Prickett <simon@crudworks.org>
This commit is contained in:
leibale
2021-10-19 16:20:02 -04:00
parent 46aad787eb
commit 2a7a7c1c2e
18 changed files with 1883 additions and 480 deletions

12
.eslintrc.json Normal file
View File

@@ -0,0 +1,12 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
]
}

View File

@@ -13,7 +13,7 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
node-version: [12.x, 14.x, 16.x] node-version: [12.x, 14.x, 16.x]
redis-version: [5.x, 6.x] redis-version: [5.x, 6.0.x, 6.2.x]
steps: steps:
- uses: actions/checkout@v2.3.4 - uses: actions/checkout@v2.3.4

View File

@@ -57,7 +57,7 @@ createClient({
}); });
``` ```
You can also use discrete parameters, UNIX sockets, and even TLS to connect. Details can be found in in the [Wiki](https://github.com/redis/node-redis/wiki/lib.socket#RedisSocketOptions). 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).
### Redis Commands ### Redis Commands
@@ -227,32 +227,34 @@ import { createClient, defineScript } from 'redis';
})(); })();
``` ```
### Cluster ### Disconnecting
Connecting to a cluster is a bit different. Create the client by specifying some (or all) of the nodes in your cluster and then use it like a non-clustered client: 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.
#### `.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.
```typescript ```typescript
import { createCluster } from 'redis'; const [ping, get, quit] = await Promise.all([
client.ping(),
client.get('key'),
client.quit()
]); // ['PONG', null, 'OK']
(async () => { try {
const cluster = createCluster({ await client.get('key');
rootNodes: [ } catch (err) {
{ // ClosedClient Error
url: 'redis://10.0.0.1:30001'
},
{
url: 'redis://10.0.0.2:30002'
} }
] ```
});
cluster.on('error', (err) => console.log('Redis Cluster Error', err)); #### `.disconnect()`
await cluster.connect(); 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.
await cluster.set('key', 'value'); ```typescript
const value = await cluster.get('key'); await client.disconnect();
})();
``` ```
### Auto-Pipelining ### Auto-Pipelining
@@ -273,6 +275,23 @@ await Promise.all([
]); ]);
``` ```
### Clustering
Check out the [Clustering Guide](./docs/clustering.md) when using Node Redis to connect to a Redis Cluster.
## Supported Redis versions
Node Redis is supported with the following versions of Redis:
| Version | Supported |
|---------|--------------------|
| 6.2.z | :heavy_check_mark: |
| 6.0.z | :heavy_check_mark: |
| 5.y.z | :heavy_check_mark: |
| < 5.0 | :x: |
> Node Redis should work with older versions of Redis, but it is not fully tested and we cannot offer support.
## Contributing ## 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).

View File

@@ -5,9 +5,9 @@
Node Redis is generally backwards compatible with very few exceptions, so we recommend users to always use the latest version to experience stability, performance and security. Node Redis is generally backwards compatible with very few exceptions, so we recommend users to always use the latest version to experience stability, performance and security.
| Version | Supported | | Version | Supported |
| ------- | ------------------ | |---------|--------------------|
| 4.0.x | :white_check_mark: | | 4.0.z | :heavy_check_mark: |
| 3.1.x | :white_check_mark: | | 3.1.z | :heavy_check_mark: |
| < 3.1 | :x: | | < 3.1 | :x: |
## Reporting a Vulnerability ## Reporting a Vulnerability

54
docs/clustering.md Normal file
View File

@@ -0,0 +1,54 @@
# Clustering
## Basic Example
Connecting to a cluster is a bit different. Create the client by specifying some (or all) of the nodes in your cluster and then use it like a regular client instance:
```typescript
import { createCluster } from 'redis';
(async () => {
const cluster = createCluster({
rootNodes: [
{
url: 'redis://10.0.0.1:30001'
},
{
url: 'redis://10.0.0.2:30002'
}
]
});
cluster.on('error', (err) => console.log('Redis Cluster Error', err));
await cluster.connect();
await cluster.set('key', 'value');
const value = await cluster.get('key');
})();
```
## `createCluster` configuration
> See the [client configuration](./client-configuration.md) page for the `rootNodes` and `defaults` configuration schemas.
| Property | Default | Description |
|------------------------|---------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| rootNodes | | An array of root nodes that are part of the cluster, which will be used to get the cluster topology. Each element in the array is a client configuration object. There is no need to specify every node in the cluster, 3 should be enough to reliably connect and obtain the cluster configuration from the server |
| defaults | | The default configuration values for every client in the cluster. Use this for example when specifying an ACL user to connect with |
| useReplicas | `false` | When `true`, distribute load by executing readonly commands (such as `GET`, `GEOSEARCH`, etc.) across all cluster nodes. When `false`, only use master nodes |
| maxCommandRedirections | `16` | The maximum number of times a command will be redirected due to `MOVED` or `ASK` errors | |
## Command Routing
### Commands that operate on Redis Keys
Commands such as `GET`, `SET`, etc. will be routed by the first key, for instance `MGET 1 2 3` will be routed by the key `1`.
### [Server Commands][https://redis.io/commands#server]
Admin commands such as `MEMORY STATS`, `FLUSHALL`, etc. are not attached to the cluster, and should be executed on a specific node using `.getSlot()` or `.getAllMasters()`.
### "Forwarded Commands"
Some commands (e.g. `PUBLISH`) are forwarded to other cluster nodes by the Redis server. The client will send these commands to a random node in order to spread the load across the cluster.

View File

@@ -6,20 +6,20 @@ import { RedisCommandRawReply } from '../commands';
export interface QueueCommandOptions { export interface QueueCommandOptions {
asap?: boolean; asap?: boolean;
chainId?: symbol; chainId?: symbol;
signal?: any; // TODO: `AbortSignal` type is incorrect signal?: AbortSignal;
} }
interface CommandWaitingToBeSent extends CommandWaitingForReply { interface CommandWaitingToBeSent extends CommandWaitingForReply {
args: Array<string | Buffer>; args: Array<string | Buffer>;
chainId?: symbol; chainId?: symbol;
abort?: { abort?: {
signal: any; // TODO: `AbortSignal` type is incorrect signal: AbortSignal;
listener(): void; listener(): void;
}; };
} }
interface CommandWaitingForReply { interface CommandWaitingForReply {
resolve(reply?: any): void; resolve(reply?: unknown): void;
reject(err: Error): void; reject(err: Error): void;
channelsCounter?: number; channelsCounter?: number;
bufferMode?: boolean; bufferMode?: boolean;
@@ -135,7 +135,8 @@ export default class RedisCommandsQueue {
signal: options.signal, signal: options.signal,
listener listener
}; };
options.signal.addEventListener('abort', listener, { // AbortSignal type is incorrent
(options.signal as any).addEventListener('abort', listener, {
once: true once: true
}); });
} }

View File

@@ -229,5 +229,5 @@ export default {
UNWATCH, UNWATCH,
unwatch: UNWATCH, unwatch: UNWATCH,
WAIT, WAIT,
wait: WAIT, wait: WAIT
}; };

View File

@@ -611,8 +611,9 @@ describe('Client', () => {
const promise = assert.rejects(client.connect(), ConnectionTimeoutError), const promise = assert.rejects(client.connect(), ConnectionTimeoutError),
start = process.hrtime.bigint(); start = process.hrtime.bigint();
while (process.hrtime.bigint() - start < 1_000_000) {
// block the event loop for 1ms, to make sure the connection will timeout // block the event loop for 1ms, to make sure the connection will timeout
while (process.hrtime.bigint() - start < 1_000_000) {} }
await promise; await promise;
} catch (err) { } catch (err) {

View File

@@ -34,16 +34,16 @@ type WithCommands = {
}; };
export type WithModules<M extends RedisModules> = { export type WithModules<M extends RedisModules> = {
[P in keyof M]: { [P in keyof M as M[P] extends never ? never : P]: {
[C in keyof M[P]]: RedisClientCommandSignature<M[P][C]>; [C in keyof M[P]]: RedisClientCommandSignature<M[P][C]>;
}; };
}; };
export type WithScripts<S extends RedisScripts> = { export type WithScripts<S extends RedisScripts> = {
[P in keyof S]: RedisClientCommandSignature<S[P]>; [P in keyof S as S[P] extends never ? never : P]: RedisClientCommandSignature<S[P]>;
}; };
export type RedisClientType<M extends RedisModules = {}, S extends RedisScripts = {}> = export type RedisClientType<M extends RedisModules = Record<string, never>, S extends RedisScripts = Record<string, never>> =
RedisClient<M, S> & WithCommands & WithModules<M> & WithScripts<S>; RedisClient<M, S> & WithCommands & WithModules<M> & WithScripts<S>;
export type InstantiableRedisClient<M extends RedisModules, S extends RedisScripts> = export type InstantiableRedisClient<M extends RedisModules, S extends RedisScripts> =
@@ -53,12 +53,14 @@ export interface ClientCommandOptions extends QueueCommandOptions {
isolated?: boolean; isolated?: boolean;
} }
type ClientLegacyCallback = (err: Error | null, reply?: RedisCommandRawReply) => void;
export default class RedisClient<M extends RedisModules, S extends RedisScripts> extends EventEmitter { export default class RedisClient<M extends RedisModules, S extends RedisScripts> extends EventEmitter {
static commandOptions(options: ClientCommandOptions): CommandOptions<ClientCommandOptions> { static commandOptions(options: ClientCommandOptions): CommandOptions<ClientCommandOptions> {
return commandOptions(options); return commandOptions(options);
} }
static extend<M extends RedisModules = {}, S extends RedisScripts = {}>(plugins?: RedisPlugins<M, S>): InstantiableRedisClient<M, S> { static extend<M extends RedisModules = Record<string, never>, S extends RedisScripts = Record<string, never>>(plugins?: RedisPlugins<M, S>): InstantiableRedisClient<M, S> {
const Client = <any>extendWithModulesAndScripts({ const Client = <any>extendWithModulesAndScripts({
BaseClass: RedisClient, BaseClass: RedisClient,
modules: plugins?.modules, modules: plugins?.modules,
@@ -74,14 +76,14 @@ export default class RedisClient<M extends RedisModules, S extends RedisScripts>
return Client; return Client;
} }
static create<M extends RedisModules = {}, S extends RedisScripts = {}>(options?: RedisClientOptions<M, S>): RedisClientType<M, S> { static create<M extends RedisModules = Record<string, never>, S extends RedisScripts = Record<string, never>>(options?: RedisClientOptions<M, S>): RedisClientType<M, S> {
return new (RedisClient.extend(options))(options); return new (RedisClient.extend(options))(options);
} }
static parseURL(url: string): RedisClientOptions<{}, {}> { static parseURL(url: string): RedisClientOptions<Record<string, never>, Record<string, never>> {
// https://www.iana.org/assignments/uri-schemes/prov/redis // https://www.iana.org/assignments/uri-schemes/prov/redis
const { hostname, port, protocol, username, password, pathname } = new URL(url), const { hostname, port, protocol, username, password, pathname } = new URL(url),
parsed: RedisClientOptions<{}, {}> = { parsed: RedisClientOptions<Record<string, never>, Record<string, never>> = {
socket: { socket: {
host: hostname host: hostname
} }
@@ -245,10 +247,12 @@ export default class RedisClient<M extends RedisModules, S extends RedisScripts>
(this as any).#v4.sendCommand = this.#sendCommand.bind(this); (this as any).#v4.sendCommand = this.#sendCommand.bind(this);
(this as any).sendCommand = (...args: Array<unknown>): void => { (this as any).sendCommand = (...args: Array<unknown>): void => {
const callback = typeof args[args.length - 1] === 'function' ? args[args.length - 1] as Function : undefined, const callback = typeof args[args.length - 1] === 'function' ?
args[args.length - 1] as ClientLegacyCallback :
undefined,
actualArgs = !callback ? args : args.slice(0, -1); actualArgs = !callback ? args : args.slice(0, -1);
this.#sendCommand(actualArgs.flat() as Array<string>) this.#sendCommand(actualArgs.flat() as Array<string>)
.then((reply: unknown) => { .then((reply: RedisCommandRawReply) => {
if (!callback) return; if (!callback) return;
// https://github.com/NodeRedis/node-redis#commands:~:text=minimal%20parsing // https://github.com/NodeRedis/node-redis#commands:~:text=minimal%20parsing
@@ -435,17 +439,12 @@ export default class RedisClient<M extends RedisModules, S extends RedisScripts>
this.#socket.cork(); this.#socket.cork();
while (true) { while (!this.#socket.writableNeedDrain) {
const args = this.#queue.getCommandToSend(); const args = this.#queue.getCommandToSend();
if (args === undefined) break; if (args === undefined) break;
let writeResult;
for (const toWrite of encodeCommand(args)) { for (const toWrite of encodeCommand(args)) {
writeResult = this.#socket.write(toWrite); this.#socket.write(toWrite);
}
if (!writeResult) {
break;
} }
} }
} }

View File

@@ -11,16 +11,16 @@ type WithCommands<M extends RedisModules, S extends RedisScripts> = {
}; };
type WithModules<M extends RedisModules, S extends RedisScripts> = { type WithModules<M extends RedisModules, S extends RedisScripts> = {
[P in keyof M]: { [P in keyof M as M[P] extends never ? never : P]: {
[C in keyof M[P]]: RedisClientMultiCommandSignature<M[P][C], M, S>; [C in keyof M[P]]: RedisClientMultiCommandSignature<M[P][C], M, S>;
}; };
}; };
type WithScripts<M extends RedisModules, S extends RedisScripts> = { type WithScripts<M extends RedisModules, S extends RedisScripts> = {
[P in keyof S]: RedisClientMultiCommandSignature<S[P], M, S> [P in keyof S as S[P] extends never ? never : P]: RedisClientMultiCommandSignature<S[P], M, S>
}; };
export type RedisClientMultiCommandType<M extends RedisModules = {}, S extends RedisScripts = {}> = export type RedisClientMultiCommandType<M extends RedisModules = Record<string, never>, S extends RedisScripts = Record<string, never>> =
RedisClientMultiCommand & WithCommands<M, S> & WithModules<M, S> & WithScripts<M, S>; RedisClientMultiCommand & WithCommands<M, S> & WithModules<M, S> & WithScripts<M, S>;
export type RedisClientMultiExecutor = (queue: Array<RedisMultiQueuedCommand>, chainId?: symbol) => Promise<Array<RedisCommandRawReply>>; export type RedisClientMultiExecutor = (queue: Array<RedisMultiQueuedCommand>, chainId?: symbol) => Promise<Array<RedisCommandRawReply>>;

View File

@@ -234,7 +234,6 @@ export default class RedisSocket extends EventEmitter {
this.#isOpen = false; this.#isOpen = false;
try { try {
await fn(); await fn();
await this.disconnect(true); await this.disconnect(true);

View File

@@ -1,5 +1,5 @@
import COMMANDS from './commands'; import COMMANDS from './commands';
import { RedisCommand, RedisCommandArguments, RedisCommandReply, RedisModules, RedisScript, RedisScripts } from '../commands'; import { RedisCommand, RedisCommandArguments, RedisCommandReply, RedisModules, RedisPlugins, RedisScript, RedisScripts } from '../commands';
import { ClientCommandOptions, RedisClientCommandSignature, RedisClientOptions, RedisClientType, WithModules, WithScripts } from '../client'; import { ClientCommandOptions, RedisClientCommandSignature, RedisClientOptions, RedisClientType, WithModules, WithScripts } from '../client';
import RedisClusterSlots, { ClusterNode } from './cluster-slots'; import RedisClusterSlots, { ClusterNode } from './cluster-slots';
import { extendWithModulesAndScripts, transformCommandArguments, transformCommandReply, extendWithCommands } from '../commander'; import { extendWithModulesAndScripts, transformCommandArguments, transformCommandReply, extendWithCommands } from '../commander';
@@ -7,14 +7,9 @@ import { EventEmitter } from 'events';
import RedisClusterMultiCommand, { RedisClusterMultiCommandType } from './multi-command'; import RedisClusterMultiCommand, { RedisClusterMultiCommandType } from './multi-command';
import { RedisMultiQueuedCommand } from '../multi-command'; import { RedisMultiQueuedCommand } from '../multi-command';
export type RedisClusterClientOptions = Omit<RedisClientOptions<{}, {}>, 'modules' | 'scripts'>; export type RedisClusterClientOptions = Omit<RedisClientOptions<Record<string, never>, Record<string, never>>, 'modules' | 'scripts'>;
export interface RedisClusterPlugins<M extends RedisModules, S extends RedisScripts> { export interface RedisClusterOptions<M extends RedisModules, S extends RedisScripts> extends RedisPlugins<M, S> {
modules?: M;
scripts?: S;
}
export interface RedisClusterOptions<M extends RedisModules, S extends RedisScripts> extends RedisClusterPlugins<M, S> {
rootNodes: Array<RedisClusterClientOptions>; rootNodes: Array<RedisClusterClientOptions>;
defaults?: Partial<RedisClusterClientOptions>; defaults?: Partial<RedisClusterClientOptions>;
useReplicas?: boolean; useReplicas?: boolean;
@@ -25,10 +20,10 @@ type WithCommands = {
[P in keyof typeof COMMANDS]: RedisClientCommandSignature<(typeof COMMANDS)[P]>; [P in keyof typeof COMMANDS]: RedisClientCommandSignature<(typeof COMMANDS)[P]>;
}; };
export type RedisClusterType<M extends RedisModules = {}, S extends RedisScripts = {}> = export type RedisClusterType<M extends RedisModules = Record<string, never>, S extends RedisScripts = Record<string, never>> =
RedisCluster<M, S> & WithCommands & WithModules<M> & WithScripts<S>; RedisCluster<M, S> & WithCommands & WithModules<M> & WithScripts<S>;
export default class RedisCluster<M extends RedisModules = {}, S extends RedisScripts = {}> extends EventEmitter { export default class RedisCluster<M extends RedisModules = Record<string, never>, S extends RedisScripts = Record<string, never>> extends EventEmitter {
static extractFirstKey(command: RedisCommand, originalArgs: Array<unknown>, redisArgs: RedisCommandArguments): string | Buffer | undefined { static extractFirstKey(command: RedisCommand, originalArgs: Array<unknown>, redisArgs: RedisCommandArguments): string | Buffer | undefined {
if (command.FIRST_KEY_INDEX === undefined) { if (command.FIRST_KEY_INDEX === undefined) {
return undefined; return undefined;
@@ -39,7 +34,7 @@ export default class RedisCluster<M extends RedisModules = {}, S extends RedisSc
return command.FIRST_KEY_INDEX(...originalArgs); return command.FIRST_KEY_INDEX(...originalArgs);
} }
static create<M extends RedisModules = {}, S extends RedisScripts = {}>(options?: RedisClusterOptions<M, S>): RedisClusterType<M, S> { static create<M extends RedisModules = Record<string, never>, S extends RedisScripts = Record<string, never>>(options?: RedisClusterOptions<M, S>): RedisClusterType<M, S> {
return new (<any>extendWithModulesAndScripts({ return new (<any>extendWithModulesAndScripts({
BaseClass: RedisCluster, BaseClass: RedisCluster,
modules: options?.modules, modules: options?.modules,

View File

@@ -12,16 +12,16 @@ type WithCommands<M extends RedisModules, S extends RedisScripts> = {
}; };
type WithModules<M extends RedisModules, S extends RedisScripts> = { type WithModules<M extends RedisModules, S extends RedisScripts> = {
[P in keyof M]: { [P in keyof M as M[P] extends never ? never : P]: {
[C in keyof M[P]]: RedisClusterMultiCommandSignature<M[P][C], M, S>; [C in keyof M[P]]: RedisClusterMultiCommandSignature<M[P][C], M, S>;
}; };
}; };
type WithScripts<M extends RedisModules, S extends RedisScripts> = { type WithScripts<M extends RedisModules, S extends RedisScripts> = {
[P in keyof S]: RedisClusterMultiCommandSignature<S[P], M, S> [P in keyof S as S[P] extends never ? never : P]: RedisClusterMultiCommandSignature<S[P], M, S>
}; };
export type RedisClusterMultiCommandType<M extends RedisModules = {}, S extends RedisScripts = {}> = export type RedisClusterMultiCommandType<M extends RedisModules = Record<string, never>, S extends RedisScripts = Record<string, never>> =
RedisClusterMultiCommand & WithCommands<M, S> & WithModules<M, S> & WithScripts<M, S>; RedisClusterMultiCommand & WithCommands<M, S> & WithModules<M, S> & WithScripts<M, S>;
export type RedisClusterMultiExecutor = (queue: Array<RedisMultiQueuedCommand>, firstKey?: string | Buffer, chainId?: symbol) => Promise<Array<RedisCommandRawReply>>; export type RedisClusterMultiExecutor = (queue: Array<RedisMultiQueuedCommand>, firstKey?: string | Buffer, chainId?: symbol) => Promise<Array<RedisCommandRawReply>>;

View File

@@ -6,4 +6,4 @@ export function transformArguments(): RedisCommandArguments {
return ['COMMAND', 'COUNT']; return ['COMMAND', 'COUNT'];
} }
declare function transformReply(): number; export declare function transformReply(): number;

View File

@@ -6,4 +6,4 @@ export function transformArguments(args: Array<string>): RedisCommandArguments {
return ['COMMAND', 'GETKEYS', ...args]; return ['COMMAND', 'GETKEYS', ...args];
} }
declare function transformReply(): Array<string>; export declare function transformReply(): Array<string>;

View File

@@ -2,43 +2,27 @@ import { RedisCommandArguments } from '.';
export const FIRST_KEY_INDEX = 1; export const FIRST_KEY_INDEX = 1;
interface EX { type MaximumOneOf<T, K extends keyof T = keyof T> =
K extends keyof T ? { [P in K]?: T[K] } & Partial<Record<Exclude<keyof T, K>, never>> : never;
type SetTTL = MaximumOneOf<{
EX: number; EX: number;
} PX: number;
interface PX {
PX: number
}
interface EXAT {
EXAT: number; EXAT: number;
}
interface PXAT {
PXAT: number; PXAT: number;
}
interface KEEPTTL {
KEEPTTL: true; KEEPTTL: true;
} }>;
type SetTTL = EX | PX | EXAT | PXAT | KEEPTTL | {}; type SetGuards = MaximumOneOf<{
interface NX {
NX: true; NX: true;
}
interface XX {
XX: true; XX: true;
} }>;
type SetGuards = NX | XX | {};
interface SetCommonOptions { interface SetCommonOptions {
GET: true GET?: true;
} }
type SetOptions = SetTTL & SetGuards & (SetCommonOptions | {}); type SetOptions = SetTTL & SetGuards & SetCommonOptions;
export function transformArguments(key: string | Buffer, value: string | Buffer, options?: SetOptions): RedisCommandArguments { export function transformArguments(key: string | Buffer, value: string | Buffer, options?: SetOptions): RedisCommandArguments {
const args = ['SET', key, value]; const args = ['SET', key, value];
@@ -47,25 +31,25 @@ export function transformArguments(key: string | Buffer, value: string | Buffer,
return args; return args;
} }
if ('EX' in options) { if (options.EX) {
args.push('EX', options.EX.toString()); args.push('EX', options.EX.toString());
} else if ('PX' in options) { } else if (options.PX) {
args.push('PX', options.PX.toString()); args.push('PX', options.PX.toString());
} else if ('EXAT' in options) { } else if (options.EXAT) {
args.push('EXAT', options.EXAT.toString()); args.push('EXAT', options.EXAT.toString());
} else if ('PXAT' in options) { } else if (options.PXAT) {
args.push('PXAT', options.PXAT.toString()); args.push('PXAT', options.PXAT.toString());
} else if ((<KEEPTTL>options).KEEPTTL) { } else if (options.KEEPTTL) {
args.push('KEEPTTL'); args.push('KEEPTTL');
} }
if ((<NX>options).NX) { if (options.NX) {
args.push('NX'); args.push('NX');
} else if ((<XX>options).XX) { } else if (options.XX) {
args.push('XX'); args.push('XX');
} }
if ((<SetCommonOptions>options).GET) { if (options.GET) {
args.push('GET'); args.push('GET');
} }

2075
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -14,6 +14,7 @@
"scripts": { "scripts": {
"test": "nyc -r text-summary -r html mocha -r source-map-support/register -r ts-node/register './lib/**/*.spec.ts'", "test": "nyc -r text-summary -r html mocha -r source-map-support/register -r ts-node/register './lib/**/*.spec.ts'",
"build": "tsc", "build": "tsc",
"lint": "eslint ./*.ts ./lib/**/*.ts",
"documentation": "typedoc" "documentation": "typedoc"
}, },
"dependencies": { "dependencies": {
@@ -26,20 +27,23 @@
"@istanbuljs/nyc-config-typescript": "^1.0.1", "@istanbuljs/nyc-config-typescript": "^1.0.1",
"@tsconfig/node12": "^1.0.9", "@tsconfig/node12": "^1.0.9",
"@types/mocha": "^9.0.0", "@types/mocha": "^9.0.0",
"@types/node": "^16.10.3", "@types/node": "^16.11.1",
"@types/sinon": "^10.0.4", "@types/sinon": "^10.0.4",
"@types/which": "^2.0.1", "@types/which": "^2.0.1",
"@types/yallist": "^4.0.1", "@types/yallist": "^4.0.1",
"mocha": "^9.1.2", "@typescript-eslint/eslint-plugin": "^5.1.0",
"@typescript-eslint/parser": "^5.1.0",
"eslint": "^8.0.1",
"mocha": "^9.1.3",
"nyc": "^15.1.0", "nyc": "^15.1.0",
"release-it": "^14.11.6", "release-it": "^14.11.6",
"sinon": "^11.1.2", "sinon": "^11.1.2",
"source-map-support": "^0.5.20", "source-map-support": "^0.5.20",
"ts-node": "^10.3.0", "ts-node": "^10.3.0",
"typedoc": "^0.22.5", "typedoc": "^0.22.6",
"typedoc-github-wiki-theme": "^0.6.0", "typedoc-github-wiki-theme": "^0.6.0",
"typedoc-plugin-markdown": "^3.11.3", "typedoc-plugin-markdown": "^3.11.3",
"typescript": "^4.4.3", "typescript": "^4.4.4",
"which": "^2.0.2" "which": "^2.0.2"
}, },
"engines": { "engines": {