Showing preview only (383K chars total). Download the full file or copy to clipboard to get everything.
Repository: denodrivers/redis
Branch: master
Commit: 1e72e0bafc47
Files: 106
Total size: 357.0 KB
Directory structure:
gitextract_mcsn30us/
├── .denov
├── .editorconfig
├── .github/
│ ├── CODEOWNERS
│ ├── FUNDING.yml
│ ├── dependabot.yml
│ ├── pinact.yaml
│ └── workflows/
│ ├── build.yml
│ └── publish.yml
├── .gitignore
├── .octocov.yml
├── LICENSE
├── README.md
├── backoff.ts
├── benchmark/
│ ├── .npmrc
│ ├── benchmark.js
│ ├── deno-redis.ts
│ ├── ioredis.js
│ └── package.json
├── client.ts
├── command.ts
├── connection.ts
├── default_client.ts
├── default_connection.ts
├── default_subscription.ts
├── deno.json
├── deps/
│ ├── cluster-key-slot.js
│ └── std/
│ ├── assert.ts
│ ├── async.ts
│ ├── bytes.ts
│ ├── collections.ts
│ ├── io.ts
│ ├── random.ts
│ └── testing.ts
├── errors.ts
├── events.ts
├── executor.ts
├── experimental/
│ ├── README.md
│ ├── cluster/
│ │ ├── README.md
│ │ └── mod.ts
│ ├── pool/
│ │ └── mod.ts
│ └── web_streams_connection/
│ └── mod.ts
├── import_map.dev.json
├── import_map.test.json
├── internal/
│ ├── buffered_readable_stream.ts
│ ├── buffered_readable_stream_test.ts
│ ├── concate_bytes.ts
│ ├── concate_bytes_test.ts
│ ├── delegate.ts
│ ├── delegate_test.ts
│ ├── encoding.ts
│ ├── on.ts
│ ├── on_test.ts
│ ├── symbols.ts
│ └── typed_event_target.ts
├── mod.ts
├── pipeline.ts
├── pool/
│ ├── client.ts
│ ├── default_pool.ts
│ ├── default_pool_test.ts
│ ├── mod.ts
│ └── pool.ts
├── protocol/
│ ├── deno_streams/
│ │ ├── command.ts
│ │ ├── mod.ts
│ │ ├── reply.ts
│ │ └── reply_test.ts
│ ├── shared/
│ │ ├── command.ts
│ │ ├── command_test.ts
│ │ ├── protocol.ts
│ │ ├── reply.ts
│ │ └── types.ts
│ └── web_streams/
│ ├── command.ts
│ ├── mod.ts
│ ├── reply.ts
│ └── reply_test.ts
├── redis.ts
├── stream.ts
├── subscription.ts
├── tests/
│ ├── backoff_test.ts
│ ├── client_test.ts
│ ├── cluster/
│ │ ├── test.ts
│ │ └── test_util.ts
│ ├── cluster_test.ts
│ ├── commands/
│ │ ├── acl.ts
│ │ ├── connection.ts
│ │ ├── general.ts
│ │ ├── geo.ts
│ │ ├── hash.ts
│ │ ├── hyper_loglog.ts
│ │ ├── key.ts
│ │ ├── latency.ts
│ │ ├── list.ts
│ │ ├── pipeline.ts
│ │ ├── pubsub.ts
│ │ ├── resp3.ts
│ │ ├── script.ts
│ │ ├── set.ts
│ │ ├── sorted_set.ts
│ │ ├── stream.ts
│ │ └── string.ts
│ ├── commands_test.ts
│ ├── pool_test.ts
│ ├── reconnect_test.ts
│ ├── server/
│ │ └── redis.conf
│ ├── test_util.ts
│ └── util_test.ts
└── tools/
└── format-benchmark-results.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .denov
================================================
2.7.7
================================================
FILE: .editorconfig
================================================
[*.ts]
indent_style = space
indent_size = 2
================================================
FILE: .github/CODEOWNERS
================================================
* @uki00a
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: [keroxp]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
cooldown:
default-days: 1
================================================
FILE: .github/pinact.yaml
================================================
# pinact - https://github.com/suzuki-shunsuke/pinact
files:
- pattern: "^\\.github/workflows/.*\\.ya?ml$"
ignore_actions:
================================================
FILE: .github/workflows/build.yml
================================================
name: CI
on:
push:
branches:
- "**"
pull_request:
branches:
- "**"
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
redis: [6.2, 7.4, 8.0]
timeout-minutes: 15
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Get Deno version
run: |
echo "DENO_VERSION=$(cat .denov)" >> $GITHUB_ENV
- name: Set up Deno ${{ env.DENO_VERSION }}
uses: denoland/setup-deno@667a34cdef165d8d2b2e98dde39547c9daac7282 # v2.0.4
with:
deno-version: ${{ env.DENO_VERSION }}
cache: true
cache-hash: ${{ hashFiles('.denov', 'deno.lock') }}
- name: Set up Redis ${{ matrix.redis }}
uses: shogo82148/actions-setup-redis@2f3253b148c73d7a0682eae73e862b777a4fa74e # v1.49.0
with:
redis-version: ${{ matrix.redis }}
auto-start: "true"
- name: Run tests
run: |
deno task test
env:
REDIS_VERSION: ${{ matrix.redis }}
- name: Run doc tests
run: |
deno task test:doc
- uses: k1LoW/octocov-action@73d561f65d59e66899ed5c87e4621a913b5d5c20 # v1.5.0
if: ${{ github.event_name == 'pull_request' && matrix.redis == 8 }}
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Get Deno version
run: |
echo "DENO_VERSION=$(cat .denov)" >> $GITHUB_ENV
- name: Set up Deno ${{ env.DENO_VERSION }}
uses: denoland/setup-deno@667a34cdef165d8d2b2e98dde39547c9daac7282 # v2.0.4
with:
deno-version: ${{ env.DENO_VERSION }}
cache: true
cache-hash: ${{ hashFiles('.denov', 'deno.lock') }}
- name: Run linters
run: |
deno task check
- name: deno publish --dry-run
run:
deno publish --dry-run
benchmark:
runs-on: ubuntu-latest
strategy:
matrix:
redis: [8.0]
driver: [deno-redis, ioredis]
timeout-minutes: 15
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Get Deno version
run: |
echo "DENO_VERSION=$(cat .denov)" >> $GITHUB_ENV
- name: Set up Deno ${{ env.DENO_VERSION }}
uses: denoland/setup-deno@667a34cdef165d8d2b2e98dde39547c9daac7282 # v2.0.4
with:
deno-version: ${{ env.DENO_VERSION }}
cache: true
cache-hash: ${{ hashFiles('.denov', 'deno.lock') }}
- name: Set up Node.js
if: matrix.driver == 'ioredis'
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: "24"
- uses: bahmutov/npm-install@20216767ca67f0f7b4d095dc5859c5700a6581cb # v1.12.1
if: matrix.driver == 'ioredis'
with:
working-directory: benchmark
- name: Set up Redis ${{ matrix.redis }}
uses: shogo82148/actions-setup-redis@2f3253b148c73d7a0682eae73e862b777a4fa74e # v1.49.0
with:
redis-version: ${{ matrix.redis }}
auto-start: "true"
- name: Run benchmarks
run: |
deno task bench:${{ matrix.driver }}
- name: Output results as a job summary
run: |
deno run --allow-read=tmp tools/format-benchmark-results.js >> $GITHUB_STEP_SUMMARY
================================================
FILE: .github/workflows/publish.yml
================================================
name: Publish to JSR
on:
release:
types: [published]
workflow_dispatch:
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Get Deno version
run: |
echo "DENO_VERSION=$(cat .denov)" >> $GITHUB_ENV
- name: Set up Deno ${{ env.DENO_VERSION }}
uses: denoland/setup-deno@667a34cdef165d8d2b2e98dde39547c9daac7282 # v2.0.4
with:
deno-version: ${{ env.DENO_VERSION }}
- name: Publish packages
run:
deno publish
================================================
FILE: .gitignore
================================================
deno.d.ts
.idea
testdata/*/
benchmark/node_modules
tests/tmp/*/
coverage
tmp
.vscode/settings.json
================================================
FILE: .octocov.yml
================================================
# generated by octocov init
codeToTestRatio:
code:
- "**/*.ts"
- "!tests/*.ts"
test:
- "tests/*.ts"
coverage:
paths:
- coverage/lcov.info
testExecutionTime:
if: true
diff:
datastores:
- artifact://${GITHUB_REPOSITORY}
comment:
# TODO: enable this
if: is_pull_request
# if: is_pull_request
summary:
if: true
report:
if: is_default_branch
datastores:
- artifact://${GITHUB_REPOSITORY}
repository: denodrivers/redis
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2018 Yusuke Sakurai
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# deno-redis
[](https://jsr.io/@db/redis)
[](https://github.com/denodrivers/redis/actions)
[](https://github.com/denodrivers/redis)
[](https://discord.gg/QXuHBMcgWx)
An experimental implementation of redis client for deno
## Usage
### Installation
```shell
$ deno add jsr:@db/redis
```
### Permissions
`deno-redis` needs `--allow-net` privilege
### Stateless Commands
```ts
import { connect } from "@db/redis";
const redis = await connect({
hostname: "127.0.0.1",
port: 6379,
});
const ok = await redis.set("hoge", "fuga");
const fuga = await redis.get("hoge");
```
### Pub/Sub
```ts
import { connect } from "@db/redis";
const redis = await connect({ hostname: "127.0.0.1" });
const sub = await redis.subscribe("channel");
(async function () {
for await (const { channel, message } of sub.receive()) {
// on message
}
})();
```
### Streams
```ts
import { connect } from "@db/redis";
const redis = await connect({ hostname: "127.0.0.1" });
await redis.xadd(
"somestream",
"*", // let redis assign message ID
{ yes: "please", no: "thankyou" },
{ elements: 10 },
);
const [stream] = await redis.xread(
[{ key: "somestream", xid: 0 }], // read from beginning
{ block: 5000 },
);
const msgFV = stream.messages[0].fieldValues;
const plz = msgFV["yes"];
const thx = msgFV["no"];
```
### Cluster
First, if you need to set up nodes into a working redis cluster:
```ts
import { connect } from "@db/redis";
const redis = await connect({ hostname: "127.0.0.1", port: 6379 });
// connect each node to form a cluster (see https://redis.io/commands/cluster-meet)
await redis.clusterMeet("127.0.0.1", 6380);
// ...
// List the nodes in the cluster
await redis.clusterNodes();
// ... 127.0.0.1:6379@16379 myself,master - 0 1593978765000 0 connected
// ... 127.0.0.1:6380@16380 master - 0 1593978766503 1 connected
```
To consume a redis cluster, you can use
[experimental/cluster module](experimental/cluster/README.md).
## Advanced Usage
### Handle connection timeout
Connection timeout handling can be implemented with `signal` option:
```ts
import { connect } from "@db/redis";
using redis = await connect({
hostname: "127.0.0.1",
signal: () => AbortSignal.timeout(5_000),
});
```
### Retriable connection
By default, a client's connection will retry a command execution based on
exponential backoff algorithm if the server dies or the network becomes
unavailable. You can change the maximum number of retries by setting
`maxRetryCount` (It's default to `10`):
```ts
import { connect } from "@db/redis";
const redis = await connect({ hostname: "127.0.0.1", maxRetryCount: 0 }); // Disable retries
```
### Execute raw commands
`Redis.sendCommand` is low level interface for
[redis protocol](https://redis.io/topics/protocol). You can send raw redis
commands and receive replies.
```ts
import { connect } from "@db/redis";
const redis = await connect({ hostname: "127.0.0.1" });
const reply = await redis.sendCommand("SET", ["redis", "nice"]);
console.assert(reply === "OK");
```
If `returnUint8Arrays` option is set to `true`, simple strings and bulk strings
are returned as `Uint8Array`
```ts
import { connect } from "@db/redis";
const redis = await connect({ hostname: "127.0.0.1" });
const reply = await redis.sendCommand("GET", ["redis"], {
returnUint8Arrays: true,
});
console.assert(reply instanceof Uint8Array);
```
### Pipelining
https://redis.io/topics/pipelining
```ts
import { connect } from "@db/redis";
const redis = await connect({ hostname: "127.0.0.1" });
const pl = redis.pipeline();
pl.ping();
pl.ping();
pl.set("set1", "value1");
pl.set("set2", "value2");
pl.mget("set1", "set2");
pl.del("set1");
pl.del("set2");
const replies = await pl.flush();
```
### TxPipeline (pipeline with MULTI/EXEC)
We recommend to use `tx()` instead of `multi()/exec()` for transactional
operation. `MULTI/EXEC` are potentially stateful operation so that operation's
atomicity is guaranteed but redis's state may change between MULTI and EXEC.
`WATCH` is designed for these problems. You can ignore it by using TxPipeline
because pipelined MULTI/EXEC commands are strictly executed in order at the time
and no changes will happen during execution.
See detail https://redis.io/topics/transactions
```ts
import { connect } from "@db/redis";
const redis = await connect({ hostname: "127.0.0.1" });
const tx = redis.tx();
tx.set("a", "aa");
tx.set("b", "bb");
tx.del("c");
await tx.flush();
// MULTI
// SET a aa
// SET b bb
// DEL c
// EXEC
```
### Client side caching
https://redis.io/topics/client-side-caching
```typescript
import { connect } from "@db/redis";
const mainClient = await connect({ hostname: "127.0.0.1" });
const cacheClient = await connect({ hostname: "127.0.0.1" });
const cacheClientID = await cacheClient.clientID();
await mainClient.clientTracking({
mode: "ON",
redirect: cacheClientID,
});
const sub = await cacheClient.subscribe<string[]>("__redis__:invalidate");
(async () => {
for await (const { channel, message } of sub.receive()) {
// Handle invalidation messages...
}
})();
```
### Connection pooling
> [!WARNING]
> This feature is still experimental and may change in the future.
`@db/redis/experimental/pool` module provides connection pooling:
```typescript
import { createPoolClient } from "@db/redis/experimental/pool";
const redis = await createPoolClient({
connection: {
hostname: "127.0.0.1",
port: 6379,
},
});
await redis.set("foo", "bar");
await redis.get("foo");
```
### Experimental features
deno-redis provides some experimental features.
See [experimental/README.md](experimental/README.md) for details.
## Roadmap for v1
- See https://github.com/denodrivers/redis/issues/78
================================================
FILE: backoff.ts
================================================
export interface Backoff {
/**
* Returns the next backoff interval in milliseconds
*/
(attempts: number): number;
}
export interface ExponentialBackoffOptions {
/**
* @default 2
*/
multiplier: number;
/**
* The maximum backoff interval in milliseconds
* @default 5000
*/
maxInterval: number;
/**
* The minimum backoff interval in milliseconds
* @default 500
*/
minInterval: number;
}
export function exponentialBackoff({
multiplier = 2,
maxInterval = 5000,
minInterval = 500,
}: Partial<ExponentialBackoffOptions> = {}): Backoff {
return (attempts) =>
Math.min(maxInterval, minInterval * (multiplier ** (attempts - 1)));
}
================================================
FILE: benchmark/.npmrc
================================================
min-release-age=1
ignore-scripts=true
================================================
FILE: benchmark/benchmark.js
================================================
import { add, complete, configure, cycle, save, suite } from "benny";
import { dirname, join } from "node:path";
export function run({
driver,
client,
outputFilename = driver,
}) {
const encoder = new TextEncoder();
return suite(
driver,
configure({ minSamples: 10 }),
add("ping", async () => {
await client.ping("HELLO");
}),
add("set & get", () => {
const value = "bar".repeat(10);
return async () => {
const key = "foo";
await client.set(key, value);
await client.get(key);
};
}),
add("set Uint8Array", () => {
const value = encoder.encode("abcde".repeat(100));
return async () => {
const key = "bytes";
await client.set(key, value);
};
}),
add("mset & mget", async () => {
await client.mset({ a: "foo", b: encoder.encode("bar") });
await client.mget("a", "b");
}),
add("zadd & zscore", async () => {
await client.zadd("zset", 1234567, "member");
await client.zscore("zset", "member");
}),
cycle(),
complete((summary) => {
const results = summary.results.map((result) => {
const {
name,
ops,
margin,
details: {
min,
max,
mean,
median,
},
samples,
} = result;
return {
name,
ops,
margin,
min,
max,
mean,
median,
samples,
};
});
console.table(results);
}),
complete(async () => {
await client.flushdb();
}),
save({
file: outputFilename,
details: true,
folder: join(
dirname(dirname(new URL(import.meta.url).pathname)),
"tmp/benchmark",
),
}),
);
}
================================================
FILE: benchmark/deno-redis.ts
================================================
import { run } from "./benchmark.js";
import { connect } from "../mod.ts";
import { connect as connectWebStreams } from "../experimental/web_streams_connection/mod.ts";
{
const redis = await connect({
hostname: "127.0.0.1",
noDelay: true,
});
try {
await run({
client: redis,
driver: "deno-redis",
});
} finally {
await redis.quit();
}
}
{
const redis = await connectWebStreams({ hostname: "127.0.0.1" });
try {
await run({
client: redis,
driver: "deno-redis (experimental/web_streams_connection)",
outputFilename:
"deno-redis-with-experimental-web-streams-based-connection",
});
} finally {
await redis.quit();
}
}
================================================
FILE: benchmark/ioredis.js
================================================
import { run } from "./benchmark.js";
import Redis from "ioredis";
const redis = new Redis();
redis.on("connect", async () => {
await run({
client: redis,
driver: "ioredis",
});
redis.disconnect();
});
================================================
FILE: benchmark/package.json
================================================
{
"name": "deno-redis-benchmark",
"version": "0.0.0",
"type": "module",
"description": "",
"keywords": [],
"author": "",
"license": "MIT",
"private": true,
"devDependencies": {
"benny": "^3.7.1",
"ioredis": "^4.22.0"
}
}
================================================
FILE: client.ts
================================================
import type { Connection, SendCommandOptions } from "./connection.ts";
import type { RedisReply, RedisValue } from "./protocol/shared/types.ts";
import type {
DefaultPubSubMessageType,
PubSubMessageType,
RedisSubscription,
SubscribeCommand,
} from "./subscription.ts";
/**
* A low-level client for Redis.
*/
export interface Client {
/**
* @deprecated
*/
readonly connection: Connection;
/**
* @deprecated
*/
exec(
command: string,
...args: RedisValue[]
): Promise<RedisReply>;
sendCommand(
command: string,
args?: RedisValue[],
options?: SendCommandOptions,
): Promise<RedisReply>;
subscribe<TMessage extends PubSubMessageType = DefaultPubSubMessageType>(
command: SubscribeCommand,
...channelsOrPatterns: Array<string>
): Promise<RedisSubscription<TMessage>>;
/**
* Closes a redis connection.
*/
close(): void;
}
================================================
FILE: command.ts
================================================
import type {
Binary,
Bulk,
BulkNil,
BulkString,
ConditionalArray,
Integer,
Protover,
Raw,
RedisValue,
SimpleString,
} from "./protocol/shared/types.ts";
import type { RedisPipeline } from "./pipeline.ts";
import type { RedisSubscription } from "./subscription.ts";
import type {
StartEndCount,
XAddFieldValues,
XClaimOpts,
XClaimReply,
XId,
XIdAdd,
XIdInput,
XIdNeg,
XIdPos,
XInfoConsumersReply,
XInfoGroupsReply,
XInfoStreamFullReply,
XInfoStreamReply,
XKeyId,
XKeyIdGroup,
XKeyIdGroupLike,
XKeyIdLike,
XMaxlen,
XMessage,
XPendingCount,
XPendingReply,
XReadGroupOpts,
XReadOpts,
XReadReply,
} from "./stream.ts";
export type ACLLogMode = "RESET";
type BitopOperation = "AND" | "OR" | "XOR" | "NOT";
export interface BitfieldOpts {
get?: { type: string; offset: number | string };
set?: { type: string; offset: number | string; value: number };
incrby?: { type: string; offset: number | string; increment: number };
}
export interface BitfieldWithOverflowOpts extends BitfieldOpts {
overflow: "WRAP" | "SAT" | "FAIL";
}
export type ClientCachingMode = "YES" | "NO";
export interface ClientKillOpts {
addr?: string; // ip:port
laddr?: string; // ip:port
id?: number;
type?: ClientType;
user?: string;
skipme?: "YES" | "NO";
}
export interface ClientListOpts {
type?: ClientType;
ids?: number[];
}
export type ClientPauseMode = "WRITE" | "ALL";
export interface ClientTrackingOpts {
mode: "ON" | "OFF";
redirect?: number;
prefixes?: string[];
bcast?: boolean;
optIn?: boolean;
optOut?: boolean;
noLoop?: boolean;
}
export type ClientType = "NORMAL" | "MASTER" | "REPLICA" | "PUBSUB";
export type ClientUnblockingBehaviour = "TIMEOUT" | "ERROR";
export type ClusterFailoverMode = "FORCE" | "TAKEOVER";
export type ClusterResetMode = "HARD" | "SOFT";
export type ClusterSetSlotSubcommand =
| "IMPORTING"
| "MIGRATING"
| "NODE"
| "STABLE";
export interface MigrateOpts {
copy?: boolean;
replace?: boolean;
auth?: string;
keys?: string[];
}
export interface RestoreOpts {
replace?: boolean;
absttl?: boolean;
idletime?: number;
freq?: number;
}
export interface HelloOpts {
protover: Protover;
auth?: {
username: string;
password: string;
};
clientName?: string;
}
export interface StralgoOpts {
idx?: boolean;
len?: boolean;
minmatchlen?: number;
withmatchlen?: boolean;
}
export type StralgoAlgorithm = "LCS";
export type StralgoTarget = "KEYS" | "STRINGS";
export interface SetOpts {
ex?: number;
px?: number;
/** Sets `NX` option. */
nx?: boolean;
/** Sets `XX` option. */
xx?: boolean;
/**
* Sets `EXAT` option.
*
* `EXAT` option was added in Redis v6.2.
*/
exat?: number;
/**
* Sets `PXAT` option.
*
* `PXAT` option was added in Redis v6.2.
*/
pxat?: number;
keepttl?: boolean;
/**
* Enables `GET` option.
*
* `GET` option was added in Redis v6.2.
*/
get?: boolean;
}
/** Return type for {@linkcode RedisCommands.set} */
export type SetReply<T extends SetOpts | SetWithModeOpts> = T extends
{ get: true } ? SimpleString | BulkNil
: T extends { nx: true } ? SimpleString | BulkNil
: T extends { xx: true } ? SimpleString | BulkNil
: T extends SetWithModeOpts ? SimpleString | BulkNil
: SimpleString;
/**
* @deprecated Use {@linkcode SetOpts.nx}/{@linkcode SetOpts.xx} instead. This type will be removed in the future.
*/
export interface SetWithModeOpts extends SetOpts {
/**
* @deprecated Use {@linkcode SetOpts.nx}/{@linkcode SetOpts.xx} instead. This option will be removed in the future.
*/
mode: "NX" | "XX";
}
export interface GeoRadiusOpts {
withCoord?: boolean;
withDist?: boolean;
withHash?: boolean;
count?: number;
sort?: "ASC" | "DESC";
store?: string;
storeDist?: string;
}
export type GeoUnit = "m" | "km" | "ft" | "mi";
interface BaseScanOpts {
pattern?: string;
count?: number;
}
export interface ScanOpts extends BaseScanOpts {
type?: string;
}
export type HScanOpts = BaseScanOpts;
export type SScanOpts = BaseScanOpts;
export type ZScanOpts = BaseScanOpts;
export interface ZAddOpts {
/** @deprecated Use {@linkcode ZAddOpts.nx}/{@linkcode ZAddOpts.xx} instead. This option will be removed in the future. */
mode?: "NX" | "XX";
ch?: boolean;
/** Enables `NX` option */
nx?: boolean;
/** Enables `XX` option */
xx?: boolean;
}
/** Return type for {@linkcode RedisCommands.zadd} */
export type ZAddReply<T extends ZAddOpts> =
// TODO: Uncomment the following:
// T extends { mode: "NX" | "XX" }
// ? Integer | BulkNil
// :
T extends { nx: true } ? Integer | BulkNil
: T extends { xx: true } ? Integer | BulkNil
: Integer;
interface ZStoreOpts {
aggregate?: "SUM" | "MIN" | "MAX";
}
export type ZInterstoreOpts = ZStoreOpts;
export type ZUnionstoreOpts = ZStoreOpts;
export interface ZRangeOpts {
withScore?: boolean;
}
export type ZInterOpts = {
withScore?: boolean;
} & ZStoreOpts;
export interface ZRangeByLexOpts {
limit?: { offset: number; count: number };
}
export interface ZRangeByScoreOpts {
withScore?: boolean;
limit?: { offset: number; count: number };
}
interface BaseLPosOpts {
rank?: number;
maxlen?: number;
}
export interface LPosOpts extends BaseLPosOpts {
count?: null | undefined;
}
export interface LPosWithCountOpts extends BaseLPosOpts {
count: number;
}
export type LInsertLocation = "BEFORE" | "AFTER";
export interface MemoryUsageOpts {
samples?: number;
}
type RoleReply =
| ["master", Integer, BulkString[][]]
| ["slave", BulkString, Integer, BulkString, Integer]
| ["sentinel", BulkString[]];
export type ScriptDebugMode = "YES" | "SYNC" | "NO";
export interface SortOpts {
by?: string;
limit?: { offset: number; count: number };
patterns?: string[];
order?: "ASC" | "DESC";
alpha?: boolean;
}
export interface SortWithDestinationOpts extends SortOpts {
destination: string;
}
export type ShutdownMode = "NOSAVE" | "SAVE";
export interface RedisCommands {
// Connection
auth(password: string): Promise<SimpleString>;
auth(username: string, password: string): Promise<SimpleString>;
echo(message: RedisValue): Promise<BulkString>;
ping(): Promise<SimpleString>;
ping(message: RedisValue): Promise<BulkString>;
quit(): Promise<SimpleString>;
select(index: number): Promise<SimpleString>;
hello(opts?: HelloOpts): Promise<ConditionalArray>;
// Keys
del(...keys: string[]): Promise<Integer>;
dump(key: string): Promise<Binary | BulkNil>;
exists(...keys: string[]): Promise<Integer>;
expire(key: string, seconds: number): Promise<Integer>;
expireat(key: string, timestamp: string): Promise<Integer>;
keys(pattern: string): Promise<BulkString[]>;
migrate(
host: string,
port: number | string,
key: string,
destination_db: string,
timeout: number,
opts?: MigrateOpts,
): Promise<SimpleString>;
move(key: string, db: string): Promise<Integer>;
objectRefCount(key: string): Promise<Integer | BulkNil>;
objectEncoding(key: string): Promise<Bulk>;
objectIdletime(key: string): Promise<Integer | BulkNil>;
objectFreq(key: string): Promise<Integer | BulkNil>;
objectHelp(): Promise<BulkString[]>;
persist(key: string): Promise<Integer>;
pexpire(key: string, milliseconds: number): Promise<Integer>;
pexpireat(key: string, milliseconds_timestamp: number): Promise<Integer>;
pttl(key: string): Promise<Integer>;
randomkey(): Promise<Bulk>;
rename(key: string, newkey: string): Promise<SimpleString>;
renamenx(key: string, newkey: string): Promise<Integer>;
restore(
key: string,
ttl: number,
serialized_value: Binary,
opts?: RestoreOpts,
): Promise<SimpleString>;
scan(
cursor: number,
opts?: ScanOpts,
): Promise<[BulkString, BulkString[]]>;
sort(
key: string,
opts?: SortOpts,
): Promise<BulkString[]>;
sort(
key: string,
opts?: SortWithDestinationOpts,
): Promise<Integer>;
touch(...keys: string[]): Promise<Integer>;
ttl(key: string): Promise<Integer>;
type(key: string): Promise<SimpleString>;
unlink(...keys: string[]): Promise<Integer>;
wait(numreplicas: number, timeout: number): Promise<Integer>;
// String
append(key: string, value: RedisValue): Promise<Integer>;
bitcount(key: string): Promise<Integer>;
bitcount(key: string, start: number, end: number): Promise<Integer>;
bitfield(
key: string,
opts?: BitfieldOpts,
): Promise<Integer[]>;
bitfield(
key: string,
opts?: BitfieldWithOverflowOpts,
): Promise<(Integer | BulkNil)[]>;
bitop(
operation: BitopOperation,
destkey: string,
...keys: string[]
): Promise<Integer>;
bitpos(
key: string,
bit: number,
start?: number,
end?: number,
): Promise<Integer>;
decr(key: string): Promise<Integer>;
decrby(key: string, decrement: number): Promise<Integer>;
get(key: string): Promise<Bulk>;
getbit(key: string, offset: number): Promise<Integer>;
getrange(key: string, start: number, end: number): Promise<BulkString>;
getset(key: string, value: RedisValue): Promise<Bulk>;
incr(key: string): Promise<Integer>;
incrby(key: string, increment: number): Promise<Integer>;
incrbyfloat(key: string, increment: number): Promise<BulkString>;
mget(...keys: string[]): Promise<Bulk[]>;
mset(key: string, value: RedisValue): Promise<SimpleString>;
mset(...key_values: [string, RedisValue][]): Promise<SimpleString>;
mset(key_values: Record<string, RedisValue>): Promise<SimpleString>;
msetnx(key: string, value: RedisValue): Promise<Integer>;
msetnx(...key_values: [string, RedisValue][]): Promise<Integer>;
msetnx(key_values: Record<string, RedisValue>): Promise<Integer>;
psetex(
key: string,
milliseconds: number,
value: RedisValue,
): Promise<SimpleString>;
set<TSetOpts extends SetOpts | SetWithModeOpts = SetOpts>(
key: string,
value: RedisValue,
opts?: TSetOpts,
): Promise<SetReply<TSetOpts>>;
setbit(key: string, offset: number, value: RedisValue): Promise<Integer>;
setex(key: string, seconds: number, value: RedisValue): Promise<SimpleString>;
setnx(key: string, value: RedisValue): Promise<Integer>;
setrange(key: string, offset: number, value: RedisValue): Promise<Integer>;
stralgo(
algorithm: StralgoAlgorithm,
target: StralgoTarget,
a: string,
b: string,
): Promise<Bulk>;
stralgo(
algorithm: StralgoAlgorithm,
target: StralgoTarget,
a: string,
b: string,
opts?: { len: true },
): Promise<Integer>;
stralgo(
algorithm: StralgoAlgorithm,
target: StralgoTarget,
a: string,
b: string,
opts?: { idx: true },
): Promise<
[
string, //`"matches"`
Array<[[number, number], [number, number]]>,
string, // `"len"`
Integer,
]
>;
stralgo(
algorithm: StralgoAlgorithm,
target: StralgoTarget,
a: string,
b: string,
opts?: { idx: true; withmatchlen: true },
): Promise<
[
string, // `"matches"`
Array<[[number, number], [number, number], number]>,
string, // `"len"`
Integer,
]
>;
stralgo(
algorithm: StralgoAlgorithm,
target: StralgoTarget,
a: string,
b: string,
opts?: StralgoOpts,
): Promise<
Bulk | Integer | [
string, // `"matches"`
Array<[[number, number], [number, number], number | undefined]>,
string, // `"len"`
Integer,
]
>;
strlen(key: string): Promise<Integer>;
// Geo
geoadd(
key: string,
longitude: number,
latitude: number,
member: string,
): Promise<Integer>;
geoadd(
key: string,
...lng_lat_members: [number, number, string][]
): Promise<Integer>;
geoadd(
key: string,
member_lng_lats: Record<string, [number, number]>,
): Promise<Integer>;
geohash(key: string, ...members: string[]): Promise<Bulk[]>;
geopos(
key: string,
...members: string[]
): Promise<Array<[BulkString, BulkString] | BulkNil | []> | BulkNil>;
geodist(
key: string,
member1: string,
member2: string,
unit?: "m" | "km" | "ft" | "mi",
): Promise<Bulk>;
// FIXME: Return type is too conditional
georadius(
key: string,
longitude: number,
latitude: number,
radius: number,
unit: GeoUnit,
opts?: GeoRadiusOpts,
): Promise<ConditionalArray>;
// FIXME: Return type is too conditional
georadiusbymember(
key: string,
member: string,
radius: number,
unit: GeoUnit,
opts?: GeoRadiusOpts,
): Promise<ConditionalArray>;
// Hash
hdel(key: string, ...fields: string[]): Promise<Integer>;
hexists(key: string, field: string): Promise<Integer>;
hget(key: string, field: string): Promise<Bulk>;
hgetall(key: string): Promise<BulkString[]>;
hincrby(key: string, field: string, increment: number): Promise<Integer>;
hincrbyfloat(
key: string,
field: string,
increment: number,
): Promise<BulkString>;
hkeys(key: string): Promise<BulkString[]>;
hlen(key: string): Promise<Integer>;
hmget(key: string, ...fields: string[]): Promise<Bulk[]>;
/**
* @deprecated since 4.0.0, use hset
*/
hmset(key: string, field: string, value: RedisValue): Promise<SimpleString>;
/**
* @deprecated since 4.0.0, use hset
*/
hmset(
key: string,
...field_values: [string, RedisValue][]
): Promise<SimpleString>;
/**
* @deprecated since 4.0.0, use hset
*/
hmset(
key: string,
field_values: Record<string, RedisValue>,
): Promise<SimpleString>;
hscan(
key: string,
cursor: number,
opts?: HScanOpts,
): Promise<[BulkString, BulkString[]]>;
/**
* @description Sets `field` in the hash to `value`.
* @see https://redis.io/commands/hset
*/
hset(key: string, field: string, value: RedisValue): Promise<Integer>;
/**
* @description Sets the field-value pairs specified by `fieldValues` to the hash stored at `key`.
* NOTE: Variadic form for `HSET` is supported only in Redis v4.0.0 or higher.
*/
hset(key: string, ...fieldValues: [string, RedisValue][]): Promise<Integer>;
/**
* @description Sets the field-value pairs specified by `fieldValues` to the hash stored at `key`.
* NOTE: Variadic form for `HSET` is supported only in Redis v4.0.0 or higher.
*/
hset(key: string, fieldValues: Record<string, RedisValue>): Promise<Integer>;
hsetnx(key: string, field: string, value: RedisValue): Promise<Integer>;
hstrlen(key: string, field: string): Promise<Integer>;
hvals(key: string): Promise<BulkString[]>;
// List
blpop(
timeout: number,
...keys: string[]
): Promise<[BulkString, BulkString] | BulkNil>;
brpop(
timeout: number,
...keys: string[]
): Promise<[BulkString, BulkString] | BulkNil>;
brpoplpush(
source: string,
destination: string,
timeout: number,
): Promise<Bulk>;
lindex(key: string, index: number): Promise<Bulk>;
linsert(
key: string,
loc: LInsertLocation,
pivot: string,
value: RedisValue,
): Promise<Integer>;
llen(key: string): Promise<Integer>;
lpop(key: string): Promise<Bulk>;
lpop(key: string, count: number): Promise<Array<BulkString>>;
/**
* Returns the index of the first matching element inside a list.
* If no match is found, this method returns `undefined`.
*/
lpos(
key: string,
element: RedisValue,
opts?: LPosOpts,
): Promise<Integer | BulkNil>;
/**
* Returns the indexes of the first N matching elements inside a list.
* If no match is found. this method returns an empty array.
*
* @param opts.count Maximum length of the indices returned by this method
*/
lpos(
key: string,
element: RedisValue,
opts: LPosWithCountOpts,
): Promise<Integer[]>;
lpush(key: string, ...elements: RedisValue[]): Promise<Integer>;
lpushx(key: string, ...elements: RedisValue[]): Promise<Integer>;
lrange(key: string, start: number, stop: number): Promise<BulkString[]>;
lrem(key: string, count: number, element: RedisValue): Promise<Integer>;
lset(key: string, index: number, element: RedisValue): Promise<SimpleString>;
ltrim(key: string, start: number, stop: number): Promise<SimpleString>;
rpop(key: string): Promise<Bulk>;
rpoplpush(source: string, destination: string): Promise<Bulk>;
rpush(key: string, ...elements: RedisValue[]): Promise<Integer>;
rpushx(key: string, ...elements: RedisValue[]): Promise<Integer>;
// HyperLogLog
pfadd(key: string, ...elements: string[]): Promise<Integer>;
pfcount(...keys: string[]): Promise<Integer>;
pfmerge(destkey: string, ...sourcekeys: string[]): Promise<SimpleString>;
// PubSub
psubscribe<TMessage extends string | string[] = string>(
...patterns: string[]
): Promise<RedisSubscription<TMessage>>;
pubsubChannels(pattern?: string): Promise<BulkString[]>;
pubsubNumsub(...channels: string[]): Promise<(BulkString | Integer)[]>;
pubsubNumpat(): Promise<Integer>;
publish(channel: string, message: RedisValue): Promise<Integer>;
subscribe<TMessage extends string | string[] = string>(
...channels: string[]
): Promise<RedisSubscription<TMessage>>;
// Set
sadd(key: string, ...members: RedisValue[]): Promise<Integer>;
scard(key: string): Promise<Integer>;
sdiff(...keys: string[]): Promise<BulkString[]>;
sdiffstore(destination: string, ...keys: string[]): Promise<Integer>;
sinter(...keys: string[]): Promise<BulkString[]>;
sinterstore(destination: string, ...keys: string[]): Promise<Integer>;
sismember(key: string, member: RedisValue): Promise<Integer>;
smembers(key: string): Promise<BulkString[]>;
smove(
source: string,
destination: string,
member: RedisValue,
): Promise<Integer>;
spop(key: string): Promise<Bulk>;
spop(key: string, count: number): Promise<BulkString[]>;
srandmember(key: string): Promise<Bulk>;
srandmember(key: string, count: number): Promise<BulkString[]>;
srem(key: string, ...members: RedisValue[]): Promise<Integer>;
sscan(
key: string,
cursor: number,
opts?: SScanOpts,
): Promise<[BulkString, BulkString[]]>;
sunion(...keys: string[]): Promise<BulkString[]>;
sunionstore(destination: string, ...keys: string[]): Promise<Integer>;
// Stream
/**
* The XACK command removes one or multiple messages
* from the pending entries list (PEL) of a stream
* consumer group. A message is pending, and as such
* stored inside the PEL, when it was delivered to
* some consumer, normally as a side effect of calling
* XREADGROUP, or when a consumer took ownership of a
* message calling XCLAIM. The pending message was
* delivered to some consumer but the server is yet not
* sure it was processed at least once. So new calls
* to XREADGROUP to grab the messages history for a
* consumer (for instance using an XId of 0), will
* return such message. Similarly the pending message
* will be listed by the XPENDING command, that
* inspects the PEL.
*
* Once a consumer successfully processes a message,
* it should call XACK so that such message does not
* get processed again, and as a side effect, the PEL
* entry about this message is also purged, releasing
* memory from the Redis server.
*
* @param key the stream key
* @param group the group name
* @param xids the ids to acknowledge
*/
xack(key: string, group: string, ...xids: XIdInput[]): Promise<Integer>;
/**
* Write a message to a stream.
*
* Returns bulk string reply, specifically:
* The command returns the XId of the added entry.
* The XId is the one auto-generated if * is passed
* as XId argument, otherwise the command just returns
* the same XId specified by the user during insertion.
* @param key write to this stream
* @param xid the XId of the entity written to the stream
* @param field_values record object or map of field value pairs
*/
xadd(
key: string,
xid: XIdAdd,
field_values: XAddFieldValues,
): Promise<XId>;
/**
* Write a message to a stream.
*
* Returns bulk string reply, specifically:
* The command returns the XId of the added entry.
* The XId is the one auto-generated if * is passed
* as XId argument, otherwise the command just returns
* the same XId specified by the user during insertion.
* @param key write to this stream
* @param xid the XId of the entity written to the stream
* @param field_values record object or map of field value pairs
* @param maxlen number of elements, and whether or not to use an approximate comparison
*/
xadd(
key: string,
xid: XIdAdd,
field_values: XAddFieldValues,
maxlen: XMaxlen,
): Promise<XId>;
/**
* In the context of a stream consumer group, this command changes the ownership of a pending message, so that the new owner is the
* consumer specified as the command argument.
*
* It returns the claimed messages unless called with the JUSTIDs
* option, in which case it returns only their XIds.
*
* This is a complex command! Read more at https://redis.io/commands/xclaim
*
<pre>
XCLAIM mystream mygroup Alice 3600000 1526569498055-0
1) 1) 1526569498055-0
2) 1) "message"
2) "orange"
</pre>
* @param key the stream name
* @param opts Various arguments for the command. The following are required:
* GROUP: the name of the consumer group which will claim the messages
* CONSUMER: the specific consumer which will claim the message
* MIN-IDLE-TIME: claim messages whose idle time is greater than this number (milliseconds)
*
* The command has multiple options which can be omitted, however
* most are mainly for internal use in order to transfer the
* effects of XCLAIM or other commands to the AOF file and to
* propagate the same effects to the slaves, and are unlikely to
* be useful to normal users:
* IDLE <ms>: Set the idle time (last time it was delivered) of the message. If IDLE is not specified, an IDLE of 0 is assumed, that is, the time count is reset because the message has now a new owner trying to process it.
* TIME <ms-unix-time>: This is the same as IDLE but instead of a relative amount of milliseconds, it sets the idle time to a specific Unix time (in milliseconds). This is useful in order to rewrite the AOF file generating XCLAIM commands.
* RETRYCOUNT <count>: Set the retry counter to the specified value. This counter is incremented every time a message is delivered again. Normally XCLAIM does not alter this counter, which is just served to clients when the XPENDING command is called: this way clients can detect anomalies, like messages that are never processed for some reason after a big number of delivery attempts.
* FORCE: Creates the pending message entry in the PEL even if certain specified XIds are not already in the PEL assigned to a different client. However the message must be exist in the stream, otherwise the XIds of non existing messages are ignored.
* JUSTXID: Return just an array of XIds of messages successfully claimed, without returning the actual message. Using this option means the retry counter is not incremented.
* @param xids the message XIds to claim
*/
xclaim(
key: string,
opts: XClaimOpts,
...xids: XIdInput[]
): Promise<XClaimReply>;
/**
* Removes the specified entries from a stream,
* and returns the number of entries deleted,
* that may be different from the number of
* XIds passed to the command in case certain
* XIds do not exist.
*
* @param key the stream key
* @param xids ids to delete
*/
xdel(key: string, ...xids: XIdInput[]): Promise<Integer>;
/**
* This command is used to create a new consumer group associated
* with a stream.
*
* <pre>
XGROUP CREATE test-man-000 test-group $ MKSTREAM
OK
</pre>
*
* See https://redis.io/commands/xgroup
* @param key stream key
* @param groupName the name of the consumer group
* @param xid The last argument is the XId of the last
* item in the stream to consider already
* delivered. In the above case we used the
* special XId '$' (that means: the XId of the
* last item in the stream). In this case
* the consumers fetching data from that
* consumer group will only see new elements
* arriving in the stream. If instead you
* want consumers to fetch the whole stream
* history, use zero as the starting XId for
* the consumer group
* @param mkstream You can use the optional MKSTREAM subcommand as the last argument after the XId to automatically create the stream, if it doesn't exist. Note that if the stream is created in this way it will have a length of 0.
*/
xgroupCreate(
key: string,
groupName: string,
xid: XIdInput | "$",
mkstream?: boolean,
): Promise<SimpleString>;
/**
* Delete a specific consumer from a group, leaving
* the group itself intact.
*
* <pre>
XGROUP DELCONSUMER test-man-000 hellogroup 4
(integer) 0
</pre>
* @param key stream key
* @param groupName the name of the consumer group
* @param consumerName the specific consumer to delete
*/
xgroupDelConsumer(
key: string,
groupName: string,
consumerName: string,
): Promise<Integer>;
/**
* Destroy a consumer group completely. The consumer
* group will be destroyed even if there are active
* consumers and pending messages, so make sure to
* call this command only when really needed.
*
<pre>
XGROUP DESTROY test-man-000 test-group
(integer) 1
</pre>
* @param key stream key
* @param groupName the consumer group to destroy
*/
xgroupDestroy(key: string, groupName: string): Promise<Integer>;
/** A support command which displays text about the
* various subcommands in XGROUP. */
xgroupHelp(): Promise<BulkString>;
/**
* Finally it possible to set the next message to deliver
* using the SETID subcommand. Normally the next XId is set
* when the consumer is created, as the last argument of
* XGROUP CREATE. However using this form the next XId can
* be modified later without deleting and creating the
* consumer group again. For instance if you want the
* consumers in a consumer group to re-process all the
* messages in a stream, you may want to set its next ID
* to 0:
<pre>
XGROUP SETID mystream consumer-group-name 0
</pre>
*
* @param key stream key
* @param groupName the consumer group
* @param xid the XId to use for the next message delivered
*/
xgroupSetID(
key: string,
groupName: string,
xid: XIdInput,
): Promise<SimpleString>;
xinfoStream(key: string): Promise<XInfoStreamReply>;
/**
* returns the entire state of the stream, including entries, groups, consumers and PELs. This form is available since Redis 6.0.
* @param key The stream key
*/
xinfoStreamFull(key: string, count?: number): Promise<XInfoStreamFullReply>;
/**
* Get as output all the consumer groups associated
* with the stream.
*
* @param key the stream key
*/
xinfoGroups(key: string): Promise<XInfoGroupsReply>;
/**
* Get the list of every consumer in a specific
* consumer group.
*
* @param key the stream key
* @param group list consumers for this group
*/
xinfoConsumers(key: string, group: string): Promise<XInfoConsumersReply>;
/**
* Returns the number of entries inside a stream. If the specified key does not exist the command returns zero, as if the stream was empty. However note that unlike other Redis types, zero-length streams are possible, so you should call TYPE or EXISTS in order to check if a key exists or not.
* @param key the stream key to inspect
*/
xlen(key: string): Promise<Integer>;
/**
* Complex command to obtain info on messages in the Pending Entries List.
*
* Outputs a summary about the pending messages in a given consumer group.
*
* @param key get pending messages on this stream key
* @param group get pending messages for this group
*/
xpending(
key: string,
group: string,
): Promise<XPendingReply>;
/**
* Output more detailed info about pending messages:
*
* - The ID of the message.
* - The name of the consumer that fetched the message and has still to acknowledge it. We call it the current owner of the message.
* - The number of milliseconds that elapsed since the last time this message was delivered to this consumer.
* - The number of times this message was delivered.
*
* If you pass the consumer argument to the command, it will efficiently filter for messages owned by that consumer.
* @param key get pending messages on this stream key
* @param group get pending messages for this group
* @param startEndCount start and end: XId range params. you may specify "-" for start and "+" for end. you must also provide a max count of messages.
* @param consumer optional, filter by this consumer as owner
*/
xpendingCount(
key: string,
group: string,
startEndCount: StartEndCount,
consumer?: string,
): Promise<XPendingCount[]>;
/**
* The command returns the stream entries matching a given
* range of XIds. The range is specified by a minimum and
* maximum ID. All the entries having an XId between the
* two specified or exactly one of the two XIds specified
* (closed interval) are returned.
*
* The command also has a reciprocal command returning
* items in the reverse order, called XREVRANGE, which
* is otherwise identical.
*
* The - and + special XIds mean respectively the minimum
* XId possible and the maximum XId possible inside a stream,
* so the following command will just return every
* entry in the stream.
<pre>
XRANGE somestream - +
</pre>
* @param key stream key
* @param start beginning XId, or -
* @param end final XId, or +
* @param count max number of entries to return
*/
xrange(
key: string,
start: XIdNeg,
end: XIdPos,
count?: number,
): Promise<XMessage[]>;
/**
* This command is exactly like XRANGE, but with the
* notable difference of returning the entries in
* reverse order, and also taking the start-end range
* in reverse order: in XREVRANGE you need to state the
* end XId and later the start ID, and the command will
* produce all the element between (or exactly like)
* the two XIds, starting from the end side.
*
* @param key the stream key
* @param start reading backwards, start from this XId. for the maximum, specify "+"
* @param end stop at this XId. for the minimum, specify "-"
* @param count max number of entries to return
*/
xrevrange(
key: string,
start: XIdPos,
end: XIdNeg,
count?: number,
): Promise<XMessage[]>;
/**
* Read data from one or multiple streams, only returning
* entries with an XId greater than the last received XId
* reported by the caller.
* @param key_xids pairs containing the stream key, and
* the XId from which to read
* @param opts optional max count of entries to return
* for each stream, and number of
* milliseconds for which to block
*/
xread(
key_xids: (XKeyId | XKeyIdLike)[],
opts?: XReadOpts,
): Promise<XReadReply>;
/**
* The XREADGROUP command is a special version of the XREAD command with support for consumer groups.
*
* @param key_ids { key, id } pairs to read
* @param opts you must specify group name and consumer name.
* those must be created using the XGROUP command,
* prior to invoking this command. you may optionally
* include a count of records to read, and the number
* of milliseconds to block
*/
xreadgroup(
key_xids: (XKeyIdGroup | XKeyIdGroupLike)[],
opts: XReadGroupOpts,
): Promise<XReadReply>;
/**
* Trims the stream to the indicated number
* of elements.
<pre>XTRIM mystream MAXLEN 1000</pre>
* @param key
* @param maxlen
*/
xtrim(key: string, maxlen: XMaxlen): Promise<Integer>;
// SortedSet
bzpopmin(
timeout: number,
...keys: string[]
): Promise<[BulkString, BulkString, BulkString] | BulkNil>;
bzpopmax(
timeout: number,
...keys: string[]
): Promise<[BulkString, BulkString, BulkString] | BulkNil>;
zadd<TZAddOpts extends ZAddOpts = ZAddOpts>(
key: string,
score: number,
member: RedisValue,
opts?: TZAddOpts,
): Promise<ZAddReply<TZAddOpts>>;
zadd<TZAddOpts extends ZAddOpts = ZAddOpts>(
key: string,
score_members: [number, RedisValue][],
opts?: TZAddOpts,
): Promise<ZAddReply<TZAddOpts>>;
zadd<TZAddOpts extends ZAddOpts = ZAddOpts>(
key: string,
member_scores: Record<string | number, number>,
opts?: TZAddOpts,
): Promise<ZAddReply<TZAddOpts>>;
zaddIncr(
key: string,
score: number,
member: RedisValue,
opts?: ZAddOpts,
): Promise<Bulk>;
zcard(key: string): Promise<Integer>;
zcount(key: string, min: number, max: number): Promise<Integer>;
zincrby(
key: string,
increment: number,
member: RedisValue,
): Promise<BulkString>;
zinter(keys: string[], opts?: ZInterOpts): Promise<Raw[]>;
zinter(key_weights: [string, number][], opts?: ZInterOpts): Promise<Raw[]>;
zinter(
key_weights: Record<string, number>,
opts?: ZInterOpts,
): Promise<Raw[]>;
zinterstore(
destination: string,
keys: string[],
opts?: ZInterstoreOpts,
): Promise<Integer>;
zinterstore(
destination: string,
key_weights: [string, number][],
opts?: ZInterstoreOpts,
): Promise<Integer>;
zinterstore(
destination: string,
key_weights: Record<string, number>,
opts?: ZInterstoreOpts,
): Promise<Integer>;
zlexcount(key: string, min: string, max: string): Promise<Integer>;
zpopmax(key: string, count?: number): Promise<BulkString[]>;
zpopmin(key: string, count?: number): Promise<BulkString[]>;
zrange(
key: string,
start: number,
stop: number,
opts?: ZRangeOpts,
): Promise<BulkString[]>;
zrangebylex(
key: string,
min: string,
max: string,
opts?: ZRangeByLexOpts,
): Promise<BulkString[]>;
zrangebyscore(
key: string,
min: number | string,
max: number | string,
opts?: ZRangeByScoreOpts,
): Promise<BulkString[]>;
zrank(key: string, member: RedisValue): Promise<Integer | BulkNil>;
zrem(key: string, ...members: RedisValue[]): Promise<Integer>;
zremrangebylex(key: string, min: string, max: string): Promise<Integer>;
zremrangebyrank(key: string, start: number, stop: number): Promise<Integer>;
zremrangebyscore(
key: string,
min: number | string,
max: number | string,
): Promise<Integer>;
zrevrange(
key: string,
start: number,
stop: number,
opts?: ZRangeOpts,
): Promise<BulkString[]>;
zrevrangebylex(
key: string,
max: string,
min: string,
opts?: ZRangeByLexOpts,
): Promise<BulkString[]>;
zrevrangebyscore(
key: string,
max: number | string,
min: number | string,
opts?: ZRangeByScoreOpts,
): Promise<BulkString[]>;
zrevrank(key: string, member: RedisValue): Promise<Integer | BulkNil>;
zscan(
key: string,
cursor: number,
opts?: ZScanOpts,
): Promise<[BulkString, BulkString[]]>;
zscore(key: string, member: RedisValue): Promise<Bulk>;
zunionstore(
destination: string,
keys: string[],
opts?: ZUnionstoreOpts,
): Promise<Integer>;
zunionstore(
destination: string,
key_weights: [string, number][],
opts?: ZUnionstoreOpts,
): Promise<Integer>;
zunionstore(
destination: string,
key_weights: Record<string, number>,
opts?: ZUnionstoreOpts,
): Promise<Integer>;
// Client
/**
* This command controls the tracking of the keys in the next command executed by the connection.
* @see https://redis.io/commands/client-caching
*/
clientCaching(mode: ClientCachingMode): Promise<SimpleString>;
/**
* Returns the name of the current connection which can be set by `clientSetName`.
* @see https://redis.io/commands/client-getname
*/
clientGetName(): Promise<Bulk>;
/**
* Returns the client ID we are redirecting our tracking notifications to.
* @see https://redis.io/commands/client-getredir
*/
clientGetRedir(): Promise<Integer>;
/**
* Returns the id of the current redis connection.
*/
clientID(): Promise<Integer>;
/**
* Returns information and statistics about the current client connection in a mostly human readable format.
* @see https://redis.io/commands/client-info
*/
clientInfo(): Promise<Bulk>;
/**
* Closes a given client connection.
* @see https://redis.io/commands/client-kill
*/
clientKill(opts?: ClientKillOpts): Promise<Integer>;
/**
* Returns information and statistics about the client connections server in a mostly human readable format.
* @see https://redis.io/commands/client-list
*/
clientList(opts?: ClientListOpts): Promise<Bulk>;
/**
* Suspend all the Redis clients for the specified amount of time (in milliseconds).
* @see https://redis.io/commands/client-pause
*/
clientPause(timeout: number, mode?: ClientPauseMode): Promise<SimpleString>;
/**
* Sets a `connectionName` to the current connection.
* You can get the name of the current connection using `clientGetName()`.
* @see https://redis.io/commands/client-setname
*/
clientSetName(connectionName: string): Promise<SimpleString>;
/**
* Enables the tracking feature for the Redis server that is used for server assisted client side caching.
* @see https://redis.io/commands/client-tracking
*/
clientTracking(opts: ClientTrackingOpts): Promise<SimpleString>;
/**
* Returns information about the current client connection's use of the server assisted client side caching feature.
* @see https://redis.io/commands/client-trackinginfo
*/
clientTrackingInfo(): Promise<ConditionalArray>;
/**
* This command can unblock, from a different connection, a client blocked in a blocking operation.
* @see https://redis.io/commands/client-unblock
*/
clientUnblock(
id: number,
behaviour?: ClientUnblockingBehaviour,
): Promise<Integer>;
/**
* Used to resume command processing for all clients that were paused by `clientPause`.
* @see https://redis.io/commands/client-unpause
*/
clientUnpause(): Promise<SimpleString>;
// Cluster
/**
* @see https://redis.io/topics/cluster-spec
*/
asking(): Promise<SimpleString>;
clusterAddSlots(...slots: number[]): Promise<SimpleString>;
clusterCountFailureReports(node_id: string): Promise<Integer>;
clusterCountKeysInSlot(slot: number): Promise<Integer>;
clusterDelSlots(...slots: number[]): Promise<SimpleString>;
clusterFailover(mode?: ClusterFailoverMode): Promise<SimpleString>;
clusterFlushSlots(): Promise<SimpleString>;
clusterForget(node_id: string): Promise<SimpleString>;
clusterGetKeysInSlot(slot: number, count: number): Promise<BulkString[]>;
clusterInfo(): Promise<BulkString>;
clusterKeySlot(key: string): Promise<Integer>;
clusterMeet(ip: string, port: number): Promise<SimpleString>;
clusterMyID(): Promise<BulkString>;
clusterNodes(): Promise<BulkString>;
clusterReplicas(node_id: string): Promise<BulkString[]>;
clusterReplicate(node_id: string): Promise<SimpleString>;
clusterReset(mode?: ClusterResetMode): Promise<SimpleString>;
clusterSaveConfig(): Promise<SimpleString>;
clusterSetSlot(
slot: number,
subcommand: ClusterSetSlotSubcommand,
node_id?: string,
): Promise<SimpleString>;
clusterSlaves(node_id: string): Promise<BulkString[]>;
clusterSlots(): Promise<ConditionalArray>;
readonly(): Promise<SimpleString>;
readwrite(): Promise<SimpleString>;
// Server
aclCat(categoryname?: string): Promise<BulkString[]>;
aclDelUser(...usernames: string[]): Promise<Integer>;
aclGenPass(bits?: number): Promise<BulkString>;
aclGetUser(username: string): Promise<(BulkString | BulkString[])[]>;
aclHelp(): Promise<BulkString[]>;
aclList(): Promise<BulkString[]>;
aclLoad(): Promise<SimpleString>;
aclLog(count: number): Promise<BulkString[]>;
aclLog(mode: ACLLogMode): Promise<SimpleString>;
aclSave(): Promise<SimpleString>;
aclSetUser(username: string, ...rules: string[]): Promise<SimpleString>;
aclUsers(): Promise<BulkString[]>;
aclWhoami(): Promise<BulkString>;
bgrewriteaof(): Promise<SimpleString>;
bgsave(): Promise<SimpleString>;
command(): Promise<
[BulkString, Integer, BulkString[], Integer, Integer, Integer][]
>;
commandCount(): Promise<Integer>;
commandGetKeys(): Promise<BulkString[]>;
commandInfo(
...command_names: string[]
): Promise<
([BulkString, Integer, BulkString[], Integer, Integer, Integer] | BulkNil)[]
>;
configGet(parameter: string): Promise<BulkString[]>;
configResetStat(): Promise<SimpleString>;
configRewrite(): Promise<SimpleString>;
configSet(parameter: string, value: string): Promise<SimpleString>;
dbsize(): Promise<Integer>;
debugObject(key: string): Promise<SimpleString>;
debugSegfault(): Promise<SimpleString>;
flushall(async?: boolean): Promise<SimpleString>;
flushdb(async?: boolean): Promise<SimpleString>;
info(section?: string): Promise<BulkString>;
lastsave(): Promise<Integer>;
memoryDoctor(): Promise<BulkString>;
memoryHelp(): Promise<BulkString[]>;
memoryMallocStats(): Promise<BulkString>;
memoryPurge(): Promise<SimpleString>;
memoryStats(): Promise<ConditionalArray>;
memoryUsage(key: string, opts?: MemoryUsageOpts): Promise<Integer>;
moduleList(): Promise<BulkString[]>;
moduleLoad(path: string, ...args: string[]): Promise<SimpleString>;
moduleUnload(name: string): Promise<SimpleString>;
monitor(): void;
replicaof(host: string, port: number): Promise<SimpleString>;
replicaofNoOne(): Promise<SimpleString>;
role(): Promise<RoleReply>;
save(): Promise<SimpleString>;
shutdown(mode?: ShutdownMode): Promise<SimpleString>;
slaveof(host: string, port: number): Promise<SimpleString>;
slaveofNoOne(): Promise<SimpleString>;
slowlog(subcommand: string, ...args: string[]): Promise<ConditionalArray>;
swapdb(index1: number, index2: number): Promise<SimpleString>;
sync(): void;
time(): Promise<[BulkString, BulkString]>;
// Scripting
eval(script: string, keys: string[], args: RedisValue[]): Promise<Raw>;
evalsha(sha1: string, keys: string[], args: RedisValue[]): Promise<Raw>;
scriptDebug(mode: ScriptDebugMode): Promise<SimpleString>;
scriptExists(...sha1s: string[]): Promise<Integer[]>;
scriptFlush(): Promise<SimpleString>;
scriptKill(): Promise<SimpleString>;
scriptLoad(script: string): Promise<SimpleString>;
// Transactions
discard(): Promise<SimpleString>;
exec(): Promise<ConditionalArray>;
multi(): Promise<SimpleString>;
unwatch(): Promise<SimpleString>;
watch(...keys: string[]): Promise<SimpleString>;
// Latency
latencyDoctor(): Promise<BulkString>;
// Pipeline
tx(): RedisPipeline;
pipeline(): RedisPipeline;
}
================================================
FILE: connection.ts
================================================
import type { Backoff } from "./backoff.ts";
import type { ConnectionEventMap } from "./events.ts";
import type { ErrorReplyError } from "./errors.ts";
import type { TypedEventTarget } from "./internal/typed_event_target.ts";
import type {
kUnstableCreateProtocol,
kUnstablePipeline,
kUnstableProtover,
kUnstableReadReply,
kUnstableStartReadLoop,
kUnstableWriteCommand,
} from "./internal/symbols.ts";
import type { Command, Protocol } from "./protocol/shared/protocol.ts";
import type {
Protover,
RedisReply,
RedisValue,
} from "./protocol/shared/types.ts";
export interface SendCommandOptions {
/**
* When this option is set, simple or bulk string replies are returned as `Uint8Array` type.
*
* @default false
*/
returnUint8Arrays?: boolean;
}
export interface Connection extends TypedEventTarget<ConnectionEventMap> {
/** @deprecated */
name: string | null;
isClosed: boolean;
isConnected: boolean;
close(): void;
[Symbol.dispose](): void;
connect(): Promise<void>;
reconnect(): Promise<void>;
sendCommand(
command: string,
args?: Array<RedisValue>,
options?: SendCommandOptions,
): Promise<RedisReply>;
/**
* @private
*/
[kUnstableReadReply](returnsUint8Arrays?: boolean): Promise<RedisReply>;
/**
* @private
*/
[kUnstableWriteCommand](command: Command): Promise<void>;
/**
* @private
*/
[kUnstablePipeline](
commands: Array<Command>,
): Promise<Array<RedisReply | ErrorReplyError>>;
/**
* @private
*/
[kUnstableStartReadLoop](
binaryMode?: boolean,
): AsyncIterableIterator<RedisReply>;
}
export interface RedisConnectionOptions {
tls?: boolean;
/**
* A list of root certificates, implies {@linkcode RedisConnectionOptions.tls}
*/
caCerts?: string[];
db?: number;
password?: string;
username?: string;
name?: string;
/**
* @default 10
*/
maxRetryCount?: number;
backoff?: Backoff;
/**
* When this option is set, a `PING` command is sent every specified number of seconds.
*/
healthCheckInterval?: number;
/**
* An {@linkcode AbortSignal} which is returned by this function is used to abort an ongoing connection process. With this option, you can implement connection timeout, etc.
*
* Works only in Deno v2.3 or later.
*/
signal?: () => AbortSignal;
/**
* If `true`, disables Nagle's algorithm.
*
* @default false
*/
noDelay?: boolean;
/**
* @private
*/
[kUnstableCreateProtocol]?: (conn: Deno.Conn) => Protocol;
/**
* @private
*/
[kUnstableProtover]?: Protover;
}
================================================
FILE: default_client.ts
================================================
import type { Client } from "./client.ts";
import type {
DefaultPubSubMessageType,
PubSubMessageType,
RedisSubscription,
SubscribeCommand,
} from "./subscription.ts";
import type { Connection, SendCommandOptions } from "./connection.ts";
import { DefaultRedisSubscription } from "./default_subscription.ts";
import type { RedisReply, RedisValue } from "./protocol/shared/types.ts";
export function createDefaultClient(connection: Connection): Client {
return new DefaultClient(connection);
}
class DefaultClient implements Client {
constructor(readonly connection: Connection) {}
exec(
command: string,
...args: RedisValue[]
): Promise<RedisReply> {
return this.connection.sendCommand(command, args);
}
sendCommand(
command: string,
args?: RedisValue[],
options?: SendCommandOptions,
) {
return this.connection.sendCommand(command, args, options);
}
async subscribe<
TMessage extends PubSubMessageType = DefaultPubSubMessageType,
>(
command: SubscribeCommand,
...channelsOrPatterns: Array<string>
): Promise<RedisSubscription<TMessage>> {
const subscription = new DefaultRedisSubscription<TMessage>(
this.connection,
);
switch (command) {
case "SUBSCRIBE":
await subscription.subscribe(...channelsOrPatterns);
break;
case "PSUBSCRIBE":
await subscription.psubscribe(...channelsOrPatterns);
break;
}
return subscription;
}
close(): void {
this.connection.close();
}
}
================================================
FILE: default_connection.ts
================================================
import type { Backoff } from "./backoff.ts";
import { exponentialBackoff } from "./backoff.ts";
import type {
Connection,
RedisConnectionOptions,
SendCommandOptions,
} from "./connection.ts";
import {
ErrorReplyError,
InvalidStateError,
isRetriableError,
} from "./errors.ts";
import type { ConnectionEventMap } from "./events.ts";
import {
kUnstableCreateProtocol,
kUnstablePipeline,
kUnstableProtover,
kUnstableReadReply,
kUnstableStartReadLoop,
kUnstableWriteCommand,
} from "./internal/symbols.ts";
import type { TypedEventTarget } from "./internal/typed_event_target.ts";
import {
createTypedEventTarget,
dispatchEvent,
} from "./internal/typed_event_target.ts";
import { kEmptyRedisArgs } from "./protocol/shared/command.ts";
import type { Command, Protocol } from "./protocol/shared/protocol.ts";
import { Protocol as DenoStreamsProtocol } from "./protocol/deno_streams/mod.ts";
import type { RedisReply, RedisValue } from "./protocol/shared/types.ts";
import { delay } from "./deps/std/async.ts";
export function createRedisConnection(
hostname: string,
port: number | string | undefined,
options: RedisConnectionOptions,
): Connection {
return new RedisConnection(hostname, port ?? 6379, options);
}
interface PendingCommand {
execute: () => Promise<RedisReply>;
resolve: (reply: RedisReply) => void;
reject: (error: unknown) => void;
}
class RedisConnection
implements Connection, TypedEventTarget<ConnectionEventMap> {
name: string | null = null;
private maxRetryCount = 10;
private readonly hostname: string;
private readonly port: number | string;
private _isClosed = false;
private _isConnected = false;
private backoff: Backoff;
private commandQueue: PendingCommand[] = [];
#conn!: Deno.Conn;
#protocol!: Protocol;
#eventTarget = createTypedEventTarget<ConnectionEventMap>();
#connectingPromise?: PromiseWithResolvers<void>;
get isClosed(): boolean {
return this._isClosed;
}
get isConnected(): boolean {
return this._isConnected;
}
get isRetriable(): boolean {
return this.maxRetryCount > 0;
}
constructor(
hostname: string,
port: number | string,
private options: RedisConnectionOptions,
) {
this.hostname = hostname;
this.port = port;
if (options.name) {
this.name = options.name;
}
if (options.maxRetryCount != null) {
this.maxRetryCount = options.maxRetryCount;
}
this.backoff = options.backoff ?? exponentialBackoff();
}
private async authenticate(
username: string | undefined,
password: string,
): Promise<void> {
try {
// TODO: Use `HELLO` instead of `AUTH`
password && username
? await this.#sendCommandImmediately("AUTH", [username, password])
: await this.#sendCommandImmediately("AUTH", [password]);
} catch (error) {
if (error instanceof ErrorReplyError) {
const authError = new AuthenticationError("Authentication failed", {
cause: error,
});
dispatchEvent(this.#eventTarget, "error", { error: authError });
throw authError;
} else {
dispatchEvent(this.#eventTarget, "error", { error });
throw error;
}
}
}
private async selectDb(
db: number | undefined = this.options.db,
): Promise<void> {
if (!db) throw new Error("The database index is undefined.");
await this.#sendCommandImmediately("SELECT", [db]);
}
private enqueueCommand(
command: PendingCommand,
) {
this.commandQueue.push(command);
if (!this.#isProcessingQueuedCommands) {
this.#isProcessingQueuedCommands = true;
this.processCommandQueue();
}
}
sendCommand(
command: string,
args?: Array<RedisValue>,
options?: SendCommandOptions,
): Promise<RedisReply> {
const execute = () =>
this.#protocol.sendCommand(
command,
args ?? kEmptyRedisArgs,
options?.returnUint8Arrays,
);
const { promise, resolve, reject } = Promise.withResolvers<RedisReply>();
this.enqueueCommand({ execute, resolve, reject });
return promise;
}
/**
* Executes a command immediately, bypassing the queue.
*/
#sendCommandImmediately(
command: string,
args?: Array<RedisValue>,
): Promise<RedisReply> {
const isConnecting = this.#connectingPromise != null;
if (!isConnecting) {
return Promise.reject(
new InvalidStateError(
`Unexpected inline command execution detected (command: ${command})`,
),
);
}
return this.#protocol.sendCommand(
command,
args ?? kEmptyRedisArgs,
);
}
addEventListener<K extends keyof ConnectionEventMap>(
type: K,
callback: (event: CustomEvent<ConnectionEventMap[K]>) => void,
options?: AddEventListenerOptions | boolean,
): void {
return this.#eventTarget.addEventListener(
type,
callback as (event: Event) => void,
options,
);
}
removeEventListener<K extends keyof ConnectionEventMap>(
type: K,
callback: (event: CustomEvent<ConnectionEventMap[K]>) => void,
options?: EventListenerOptions | boolean,
): void {
return this.#eventTarget.removeEventListener(
type,
callback as (event: Event) => void,
options,
);
}
[kUnstableReadReply](returnsUint8Arrays?: boolean): Promise<RedisReply> {
return this.#protocol.readReply(returnsUint8Arrays);
}
[kUnstablePipeline](commands: Array<Command>): Promise<RedisReply[]> {
const { promise, resolve, reject } = Promise.withResolvers<
RedisReply[]
>();
const execute = () => this.#protocol.pipeline(commands);
this.enqueueCommand({ execute, resolve, reject } as PendingCommand);
return promise;
}
[kUnstableWriteCommand](command: Command): Promise<void> {
return this.#protocol.writeCommand(command);
}
async *[kUnstableStartReadLoop](
binaryMode?: boolean,
): AsyncIterableIterator<RedisReply> {
let forceReconnect = false;
while (this.isConnected) {
try {
let rep: RedisReply;
try {
rep = await this[kUnstableReadReply](binaryMode);
} catch (err) {
if (this.isClosed) {
// Connection already closed by the user.
break;
}
throw err; // Connection may have been unintentionally closed.
}
yield rep;
} catch (error) {
if (isRetriableError(error)) {
forceReconnect = true;
} else throw error;
} finally {
if ((!this.isClosed && !this.isConnected) || forceReconnect) {
forceReconnect = false;
await this.reconnect();
}
}
}
}
/**
* Connect to Redis server
*/
connect(): Promise<void> {
if (this.#connectingPromise) {
return this.#connectingPromise.promise;
}
const promiseWithResolvers = Promise.withResolvers<void>();
this.#connectingPromise = promiseWithResolvers;
(async () => {
try {
await this.#connect(0);
promiseWithResolvers.resolve();
this.#connectingPromise = undefined;
} catch (error) {
promiseWithResolvers.reject(error);
this.#connectingPromise = undefined;
}
})();
return promiseWithResolvers.promise;
}
async #connect(retryCount: number) {
try {
const signal: AbortSignal | undefined = this.options?.signal?.() ??
undefined;
const dialOpts: Deno.ConnectOptions = {
hostname: this.hostname,
port: parsePortLike(this.port),
signal,
};
const conn = this.options?.tls || this.options?.caCerts != null
? await Deno.connectTls({
...dialOpts,
caCerts: this.options?.caCerts,
})
: await Deno.connect(dialOpts);
if (this.options?.noDelay && "setNoDelay" in conn) {
conn.setNoDelay();
}
this.#conn = conn;
this.#protocol = this.options?.[kUnstableCreateProtocol]?.(conn) ??
new DenoStreamsProtocol(conn);
this._isClosed = false;
this._isConnected = true;
dispatchEvent(this.#eventTarget, "connect", undefined);
try {
if (this.options.password != null) {
await this.authenticate(this.options.username, this.options.password);
}
if (this.options[kUnstableProtover] != null) {
await this.#sendCommandImmediately("HELLO", [
this.options[kUnstableProtover],
]);
}
if (this.options.db) {
await this.selectDb(this.options.db);
}
} catch (error) {
this.#close();
throw error;
}
dispatchEvent(this.#eventTarget, "ready", undefined);
this.#enableHealthCheckIfNeeded();
} catch (error) {
if (error instanceof AuthenticationError) {
dispatchEvent(this.#eventTarget, "error", { error });
dispatchEvent(this.#eventTarget, "end", undefined);
throw (error.cause ?? error);
}
const backoff = this.backoff(retryCount);
retryCount++;
if (retryCount >= this.maxRetryCount) {
dispatchEvent(this.#eventTarget, "error", { error: error as Error });
dispatchEvent(this.#eventTarget, "end", undefined);
throw error;
}
dispatchEvent(this.#eventTarget, "reconnecting", { delay: backoff });
await delay(backoff);
await this.#connect(retryCount);
}
}
close() {
return this[Symbol.dispose]();
}
[Symbol.dispose](): void {
return this.#close(false);
}
#close(canReconnect = false) {
const isClosedAlready = this._isClosed;
this._isClosed = true;
this._isConnected = false;
try {
this.#conn!.close();
} catch (error) {
if (!(error instanceof Deno.errors.BadResource)) {
dispatchEvent(this.#eventTarget, "error", { error: error as Error });
throw error;
}
} finally {
if (!isClosedAlready) {
dispatchEvent(this.#eventTarget, "close", undefined);
if (!canReconnect) {
dispatchEvent(this.#eventTarget, "end", undefined);
}
}
}
}
async reconnect(): Promise<void> {
try {
await this.sendCommand("PING");
this._isConnected = true;
} catch (error) {
dispatchEvent(this.#eventTarget, "error", { error });
this.#close(true);
await this.connect();
await this.sendCommand("PING");
}
}
#isProcessingQueuedCommands = false;
private async processCommandQueue() {
const [command] = this.commandQueue;
if (!command) {
this.#isProcessingQueuedCommands = false;
return;
}
try {
const reply = await command.execute();
command.resolve(reply);
} catch (error) {
if (
!isRetriableError(error) ||
this.isManuallyClosedByUser()
) {
dispatchEvent(this.#eventTarget, "error", { error });
return command.reject(error);
}
let backoff = 0;
for (let i = 0; i < this.maxRetryCount; i++) {
// Try to reconnect to the server and retry the command
this.#close(true);
try {
dispatchEvent(this.#eventTarget, "reconnecting", { delay: backoff });
await this.connect();
const reply = await command.execute();
return command.resolve(reply);
} catch (error) {
dispatchEvent(this.#eventTarget, "error", { error }); // TODO: use `AggregateError`?
backoff = this.backoff(i);
await delay(backoff);
}
}
dispatchEvent(this.#eventTarget, "error", { error });
command.reject(error);
} finally {
this.commandQueue.shift();
this.processCommandQueue();
}
}
private isManuallyClosedByUser(): boolean {
return this._isClosed && !this._isConnected;
}
#enableHealthCheckIfNeeded() {
const { healthCheckInterval } = this.options;
if (healthCheckInterval == null) {
return;
}
const ping = async () => {
if (this.isManuallyClosedByUser()) {
return;
}
try {
await this.sendCommand("PING");
this._isConnected = true;
} catch (_error) {
// TODO: notify the user of an error
this._isConnected = false;
} finally {
setTimeout(ping, healthCheckInterval);
}
};
setTimeout(ping, healthCheckInterval);
}
}
class AuthenticationError extends Error {}
function parsePortLike(port: string | number | undefined): number {
let parsedPort: number;
if (typeof port === "string") {
parsedPort = parseInt(port);
} else if (typeof port === "number") {
parsedPort = port;
} else {
parsedPort = 6379;
}
if (!Number.isSafeInteger(parsedPort)) {
throw new Error("Port is invalid");
}
return parsedPort;
}
================================================
FILE: default_subscription.ts
================================================
import { decoder } from "./internal/encoding.ts";
import {
kUnstableStartReadLoop,
kUnstableWriteCommand,
} from "./internal/symbols.ts";
import type {
DefaultPubSubMessageType,
PubSubMessageType,
RedisPubSubMessage,
RedisSubscription,
} from "./subscription.ts";
import type { Connection } from "./connection.ts";
import type { Binary } from "./protocol/shared/types.ts";
export class DefaultRedisSubscription<
TMessage extends PubSubMessageType = DefaultPubSubMessageType,
> implements RedisSubscription<TMessage> {
get isConnected(): boolean {
return this.connection.isConnected;
}
get isClosed(): boolean {
return this.connection.isClosed;
}
private channels = Object.create(null);
private patterns = Object.create(null);
constructor(private readonly connection: Connection) {}
async psubscribe(...patterns: string[]) {
await this.#writeCommand("PSUBSCRIBE", patterns);
for (const pat of patterns) {
this.patterns[pat] = true;
}
}
async punsubscribe(...patterns: string[]) {
await this.#writeCommand("PUNSUBSCRIBE", patterns);
for (const pat of patterns) {
delete this.patterns[pat];
}
}
async subscribe(...channels: string[]) {
await this.#writeCommand("SUBSCRIBE", channels);
for (const chan of channels) {
this.channels[chan] = true;
}
}
async unsubscribe(...channels: string[]) {
await this.#writeCommand("UNSUBSCRIBE", channels);
for (const chan of channels) {
delete this.channels[chan];
}
}
receive(): AsyncIterableIterator<RedisPubSubMessage<TMessage>> {
return this.#receive(false);
}
receiveBuffers(): AsyncIterableIterator<RedisPubSubMessage<Binary>> {
return this.#receive(true);
}
async *#receive<
T = TMessage,
>(
binaryMode: boolean,
): AsyncIterableIterator<
RedisPubSubMessage<T>
> {
const onConnectionRecovered = async () => {
if (Object.keys(this.channels).length > 0) {
await this.subscribe(...Object.keys(this.channels));
}
if (Object.keys(this.patterns).length > 0) {
await this.psubscribe(...Object.keys(this.patterns));
}
};
this.connection.addEventListener("connect", onConnectionRecovered);
const iter = this.connection[kUnstableStartReadLoop](binaryMode);
try {
for await (const _rep of iter) {
const rep = _rep as ([string | Binary, string | Binary, T] | [
string | Binary,
string | Binary,
string | Binary,
T,
]);
const event = rep[0] instanceof Uint8Array
? decoder.decode(rep[0])
: rep[0];
if (event === "message" && rep.length === 3) {
const channel = rep[1] instanceof Uint8Array
? decoder.decode(rep[1])
: rep[1];
const message = rep[2];
yield {
channel,
message,
};
} else if (event === "pmessage" && rep.length === 4) {
const pattern = rep[1] instanceof Uint8Array
? decoder.decode(rep[1])
: rep[1];
const channel = rep[2] instanceof Uint8Array
? decoder.decode(rep[2])
: rep[2];
const message = rep[3];
yield {
pattern,
channel,
message,
};
}
}
} finally {
this.connection.removeEventListener(
"connect",
onConnectionRecovered,
);
}
}
close() {
this.connection.close();
}
async #writeCommand(command: string, args: Array<string>): Promise<void> {
await this.connection[kUnstableWriteCommand]({ command, args });
}
}
================================================
FILE: deno.json
================================================
{
"name": "@db/redis",
"version": "0.41.0",
"exports": {
".": "./mod.ts",
"./experimental/pool": "./experimental/pool/mod.ts",
"./experimental/cluster": "./experimental/cluster/mod.ts",
"./experimental/web-streams-connection": "./experimental/web_streams_connection/mod.ts"
},
"exclude": [
"benchmark/node_modules",
"tmp"
],
"lint": {
"exclude": [
"benchmark/benchmark.js",
"benchmark/ioredis.js"
],
"plugins": ["jsr:@uki00a/deno-lint-plugin-extra-rules@0.9.0"],
"rules": { "include": ["no-console"] }
},
"test": {
"exclude": ["benchmark/", "vendor/"],
"permissions": {
"net": ["127.0.0.1"],
"read": ["tests"],
"write": ["tests/tmp"],
"run": ["redis-server", "redis-cli"],
"env": ["REDIS_VERSION"]
}
},
"permissions": {
"bench:deno-redis": {
"net": ["127.0.0.1:6379"],
"env": [
"NODE_DISABLE_COLORS",
"TERM",
"TERM_PROGRAM",
"GRACEFUL_FS_PLATFORM",
"TEST_GRACEFUL_FS_GLOBAL_PATCH"
],
"write": ["tmp"]
}
},
"publish": {
"exclude": [
".denov",
".editorconfig",
".octocov.yml",
".github",
"import_map.dev.json",
"import_map.test.json",
"benchmark/",
"tests/",
"**/*_test.ts",
"tools/"
]
},
"minimumDependencyAge": 1440,
"tasks": {
"lock": "deno cache --reload --import-map=import_map.dev.json mod.ts experimental/**/mod.ts tests/**/*.ts benchmark/*.ts",
"check": {
"dependencies": ["check:lint", "check:deno.json", "typecheck"]
},
"check:lint": "deno lint && deno fmt --check",
"check:deno.json": "deno run -R=deno.json --import-map=import_map.dev.json @uki00a/deno-json-lint",
"typecheck": "cat deno.json | jq '.exports.[]' | xargs deno check",
"test": "DENO_FUTURE=1 deno test --permission-set --coverage=coverage --trace-leaks --frozen-lockfile",
"test:doc": "deno check --doc-only --import-map=import_map.test.json README.md experimental/cluster/README.md",
"bench:deno-redis": "DENO_NO_PACKAGE_JSON=1 deno run --unstable --permission-set=bench:deno-redis --import-map=import_map.dev.json benchmark/deno-redis.ts",
"bench:ioredis": "node benchmark/ioredis.js"
},
"deno-json-lint": {
"rules": {
"no-restricted-fields": ["error", {
"fields": {
"imports": "Use `import_map.dev.json` instead."
}
}]
}
}
}
================================================
FILE: deps/cluster-key-slot.js
================================================
export { default as calculateSlot } from "npm:cluster-key-slot@1.1.0";
================================================
FILE: deps/std/assert.ts
================================================
export * from "jsr:@std/assert@^1";
================================================
FILE: deps/std/async.ts
================================================
export * from "jsr:@std/async@^1/delay";
================================================
FILE: deps/std/bytes.ts
================================================
export * from "jsr:@std/bytes@^1/concat";
================================================
FILE: deps/std/collections.ts
================================================
export { distinctBy } from "jsr:@std/collections@^1/distinct-by";
================================================
FILE: deps/std/io.ts
================================================
export { BufReader } from "jsr:@std/io@0.224.5/buf-reader";
export { BufWriter } from "jsr:@std/io@0.224.5/buf-writer";
export { readerFromStreamReader } from "jsr:@std/io@0.224.5/reader-from-stream-reader";
export { readAll } from "jsr:@std/io@0.224.5/read-all";
================================================
FILE: deps/std/random.ts
================================================
export { sample } from "jsr:@std/random@0.1.0/sample";
export { shuffle } from "jsr:@std/random@0.1.0/shuffle";
================================================
FILE: deps/std/testing.ts
================================================
export * from "jsr:@std/testing@^1/bdd";
export type * from "jsr:@std/testing@^1/types";
export { assertType } from "jsr:@std/testing@^1/types";
================================================
FILE: errors.ts
================================================
export class EOFError extends Error {}
export class ConnectionClosedError extends Error {}
export class SubscriptionClosedError extends Error {}
export class ErrorReplyError extends Error {}
export class NotImplementedError extends Error {
constructor(message?: string) {
super(message ? `Not implemented: ${message}` : "Not implemented");
}
}
export class InvalidStateError extends Error {
constructor(message?: string) {
const base = "Invalid state";
super(message ? `${base}: ${message}` : base);
}
}
export function isRetriableError(error: unknown): boolean {
return (error instanceof Deno.errors.BadResource ||
error instanceof Deno.errors.BrokenPipe ||
error instanceof Deno.errors.ConnectionAborted ||
error instanceof Deno.errors.ConnectionRefused ||
error instanceof Deno.errors.ConnectionReset ||
error instanceof Deno.errors.UnexpectedEof ||
error instanceof EOFError);
}
================================================
FILE: events.ts
================================================
export type ConnectionEvent = Record<string, unknown>;
export type ConnectionErrorEventDetails = {
error: unknown;
};
export type ConnectionReconnectingEventDetails = {
delay: number;
};
export type ConnectionEventMap = {
error: ConnectionErrorEventDetails;
connect: unknown;
reconnecting: ConnectionReconnectingEventDetails;
ready: unknown;
close: unknown;
end: unknown;
};
export type ConnectionEventType =
| "error"
| "connect"
| "reconnecting"
| "ready"
| "close"
| "end";
================================================
FILE: executor.ts
================================================
import type { Client } from "./client.ts";
/** @deprecated Use {@linkcode Client} instead. */
export type CommandExecutor = Client;
================================================
FILE: experimental/README.md
================================================
# Experimental features
deno-redis has some experimental features:
- [Redis Cluster client](cluster/README.md)
**These experimental features may be subject to breaking changes even after a
major release.**
================================================
FILE: experimental/cluster/README.md
================================================
# experimental/cluster
[](https://doc.deno.land/https/deno.land/x/redis/experimental/cluster/mod.ts)
This module provides a client impelementation for
[the Redis Cluster](https://redis.io/topics/cluster-tutorial).
The implementation is based on the
[antirez/redis-rb-cluster](https://github.com/antirez/redis-rb-cluster).
## Usage
```typescript
import { connect } from "@db/redis/experimental/cluster";
const cluster = await connect({
nodes: [
{
hostname: "127.0.0.1",
port: 7000,
},
{
hostname: "127.0.0.1",
port: 7001,
},
],
});
await cluster.get("{foo}bar");
```
================================================
FILE: experimental/cluster/mod.ts
================================================
/**
* @module
* @experimental **NOTE**: This is an unstable module.
*
* Based on https://github.com/antirez/redis-rb-cluster which is licensed as follows:
*
* Copyright (C) 2013 Salvatore Sanfilippo <antirez@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { connect, create } from "../../redis.ts";
import type { RedisConnectOptions } from "../../redis.ts";
import type { Client } from "../../client.ts";
import type {
DefaultPubSubMessageType,
PubSubMessageType,
RedisSubscription,
SubscribeCommand,
} from "../../subscription.ts";
import type { Connection, SendCommandOptions } from "../../connection.ts";
import type { Redis } from "../../redis.ts";
import type { RedisReply, RedisValue } from "../../protocol/shared/types.ts";
import { ErrorReplyError, NotImplementedError } from "../../errors.ts";
import { delay } from "../../deps/std/async.ts";
import { distinctBy } from "../../deps/std/collections.ts";
import { sample, shuffle } from "../../deps/std/random.ts";
import { calculateSlot } from "../../deps/cluster-key-slot.js";
export interface ClusterConnectOptions {
nodes: Array<NodeOptions>;
maxConnections?: number;
newRedis?: (opts: RedisConnectOptions) => Promise<Redis>;
}
export interface NodeOptions {
hostname: string;
port?: number;
}
interface SlotMap {
[slot: number]: ClusterNode;
}
class ClusterNode {
readonly name: string;
constructor(readonly hostname: string, readonly port: number) {
this.name = `${hostname}:${port}`;
}
static parseIPAndPort(ipAndPort: string): ClusterNode {
const [ip, port] = ipAndPort.split(":");
const node = new ClusterNode(
ip,
parseInt(port),
);
return node;
}
}
const kRedisClusterRequestTTL = 16;
class ClusterError extends Error {}
class ClusterClient implements Client {
#nodeBySlot!: SlotMap;
#startupNodes: ClusterNode[];
#refreshTableASAP?: boolean;
#maxConnections: number;
#connectionByNodeName: { [name: string]: Redis } = {};
#newRedis: (opts: RedisConnectOptions) => Promise<Redis>;
constructor(opts: ClusterConnectOptions) {
this.#startupNodes = opts.nodes.map((node) =>
new ClusterNode(node.hostname, node.port ?? 6379)
);
this.#maxConnections = opts.maxConnections ?? 50; // TODO(uki00a): To be honest, I'm not sure if this default value is appropriate...
this.#newRedis = opts.newRedis ?? connect;
}
get connection(): Connection {
throw new NotImplementedError("Not implemented yet");
}
exec(command: string, ...args: RedisValue[]): Promise<RedisReply> {
return this.sendCommand(command, args);
}
async sendCommand(
command: string,
_args?: RedisValue[],
options?: SendCommandOptions,
): Promise<RedisReply> {
if (this.#refreshTableASAP) {
await this.initializeSlotsCache();
}
let asking = false;
let askingNode: ClusterNode | null = null;
let tryRandomNode = false;
let ttl = kRedisClusterRequestTTL;
let lastError: null | Error;
const args = _args ?? [];
while (ttl > 0) {
ttl -= 1;
const key = getKeyFromCommand(command, args);
if (key == null) {
throw new ClusterError(
"No way to dispatch this command to Redis Cluster.",
);
}
const slot = calculateSlot(key);
let r: Redis;
if (asking && askingNode) {
r = await this.#getConnectionByNode(askingNode);
askingNode = null;
} else if (tryRandomNode) {
r = await this.#getRandomConnection();
tryRandomNode = false;
} else {
r = await this.#getConnectionBySlot(slot);
}
try {
if (asking) {
await r.asking();
}
asking = false;
const reply = await r.sendCommand(command, args, options);
return reply;
} catch (err) {
if (err instanceof Error) {
lastError = err;
} else {
throw err; // An unexpected error occurred.
}
if (err instanceof Deno.errors.BadResource) {
tryRandomNode = true;
if (ttl < kRedisClusterRequestTTL / 2) {
await delay(100);
}
continue;
} else if (err instanceof ErrorReplyError) {
const [code, newSlot, ipAndPort] = err.message.split(/\s+/);
if (code === "-MOVED" || code === "-ASK") {
if (code === "-ASK") {
asking = true;
} else {
// Server replied with MOVED. It's better for us to
// ask for CLUSTER NODES the next time.
this.#refreshTableASAP = true;
}
const node = ClusterNode.parseIPAndPort(ipAndPort);
if (asking) {
// Server replied with -ASK. We should send the next query to the redirected node.
askingNode = node;
} else {
this.#nodeBySlot[parseInt(newSlot)] = node;
}
} else {
throw err;
}
} else {
throw err; // An unexpected error occurred.
}
}
}
throw new ClusterError(
`Too many Cluster redirections? (last error: ${
lastError!.message ??
""
})`,
);
}
subscribe<TMessage extends PubSubMessageType = DefaultPubSubMessageType>(
_command: SubscribeCommand,
..._channelsOrPatterns: Array<string>
): Promise<RedisSubscription<TMessage>> {
return Promise.reject(new NotImplementedError("ClusterClient#subscribe"));
}
close(): void {
const nodeNames = Object.keys(this.#connectionByNodeName);
for (const nodeName of nodeNames) {
const conn = this.#connectionByNodeName[nodeName];
if (conn) {
conn.close();
delete this.#connectionByNodeName[nodeName];
}
}
this.#refreshTableASAP = true;
}
async initializeSlotsCache(): Promise<void> {
for (const node of this.#startupNodes) {
try {
const redis = await this.#getRedisLink(node);
try {
const clusterSlots = await redis.clusterSlots() as Array<
[number, number, [string, number]]
>;
const nodes = [] as ClusterNode[];
const slotMap = {} as SlotMap;
for (const [from, to, master] of clusterSlots) {
for (let slot = from; slot <= to; slot++) {
const [ip, port] = master;
const node = new ClusterNode(ip, port);
nodes.push(node);
slotMap[slot] = node;
}
}
this.#nodeBySlot = slotMap;
await this.#populateStartupNodes(nodes);
this.#refreshTableASAP = false;
return;
} finally {
await redis.quit();
}
} catch (_err) {
// TODO: Consider logging `_err` here
continue;
}
}
}
#populateStartupNodes(nodes: ClusterNode[]) {
for (const node of nodes) {
this.#startupNodes.push(node);
}
this.#startupNodes = distinctBy(
this.#startupNodes,
(node: ClusterNode) => node.name,
);
}
async #getRandomConnection(): Promise<Redis> {
for (const node of shuffle(this.#startupNodes)) {
try {
let conn = this.#connectionByNodeName[node.name];
if (conn) {
const message = await conn.ping();
if (message === "PONG") {
return conn;
}
} else {
conn = await this.#getRedisLink(node);
try {
const message = await conn.ping();
if (message === "PONG") {
await this.#closeExistingConnection();
this.#connectionByNodeName[node.name] = conn;
return conn;
} else {
await conn.quit();
}
} catch {
conn.close();
}
}
} catch {
// Just try with the next node.
}
}
throw new ClusterError("Can't reach a single startup node.");
}
#getConnectionBySlot(slot: number): Promise<Redis> {
const node = this.#nodeBySlot[slot];
if (node) {
return this.#getConnectionByNode(node);
} else {
return this.#getRandomConnection();
}
}
async #getConnectionByNode(node: ClusterNode): Promise<Redis> {
let conn = this.#connectionByNodeName[node.name];
if (conn) {
return conn;
} else {
try {
await this.#closeExistingConnection();
conn = await this.#getRedisLink(node);
this.#connectionByNodeName[node.name] = conn;
return conn;
} catch {
return this.#getRandomConnection();
}
}
}
async #closeExistingConnection() {
const nodeNames = Object.keys(this.#connectionByNodeName);
while (nodeNames.length >= this.#maxConnections) {
const nodeName = sample(nodeNames)!;
const conn = this.#connectionByNodeName[nodeName];
delete this.#connectionByNodeName[nodeName];
try {
await conn.quit();
} catch (err) {
// deno-lint-ignore no-console -- TODO: consider improving logging
console.error(err);
}
}
}
#getRedisLink(node: ClusterNode): Promise<Redis> {
const { hostname, port } = node;
return this.#newRedis({ hostname, port });
}
}
function getKeyFromCommand(command: string, args: RedisValue[]): string | null {
switch (command.toLowerCase()) {
case "info":
case "multi":
case "exec":
case "slaveof":
case "config":
case "shutdown":
return null;
default:
return args[0] as string;
}
}
/**
* Connects to the Redis Cluster.
*
* @see https://redis.io/topics/cluster-tutorial
* @see https://redis.io/topics/cluster-spec
*/
async function connectToCluster(opts: ClusterConnectOptions): Promise<Redis> {
const client = new ClusterClient(opts);
await client.initializeSlotsCache();
return create(client);
}
export { connectToCluster as connect };
================================================
FILE: experimental/pool/mod.ts
================================================
export * from "../../pool/mod.ts";
================================================
FILE: experimental/web_streams_connection/mod.ts
================================================
/**
* @module
* @experimental **NOTE**: This is an unstable module.
*/
import { kUnstableCreateProtocol } from "../../internal/symbols.ts";
import type { Redis, RedisConnectOptions } from "../../redis.ts";
import { connect as _connect } from "../../redis.ts";
import { Protocol } from "../../protocol/web_streams/mod.ts";
function createProtocol(conn: Deno.Conn) {
return new Protocol(conn);
}
export function connect(options: RedisConnectOptions): Promise<Redis> {
return _connect({
...options,
[kUnstableCreateProtocol]: createProtocol,
});
}
================================================
FILE: import_map.dev.json
================================================
{
"imports": {
"benny": "npm:benny@3.7.1",
"@uki00a/deno-json-lint": "jsr:@uki00a/deno-json-lint@^0.4.0"
}
}
================================================
FILE: import_map.test.json
================================================
{
"imports": {
"https://deno.land/x/redis/mod.ts": "./mod.ts",
"https://deno.land/x/redis/experimental/cluster/mod.ts": "./experimental/cluster/mod.ts"
}
}
================================================
FILE: internal/buffered_readable_stream.ts
================================================
import { concateBytes } from "./concate_bytes.ts";
const LF = "\n".charCodeAt(0);
/**
* Wraps `ReadableStream` to provide buffering. Heavily inspired by `deno_std/io/buf_reader.ts`.
*
* {@link https://github.com/denoland/deno_std/blob/0.204.0/io/buf_reader.ts}
*/
export class BufferedReadableStream {
#reader: ReadableStreamBYOBReader;
#buffer: Uint8Array;
constructor(readable: ReadableStream<Uint8Array>) {
this.#reader = readable.getReader({ mode: "byob" });
this.#buffer = new Uint8Array(0);
}
async readLine(): Promise<Uint8Array> {
const i = this.#buffer.indexOf(LF);
if (i > -1) {
return this.#consume(i + 1);
}
for (;;) {
await this.#fill();
const i = this.#buffer.indexOf(LF);
if (i > -1) return this.#consume(i + 1);
}
}
async readN(n: number): Promise<Uint8Array> {
if (n <= this.#buffer.length) {
return this.#consume(n);
}
if (n === 0) {
return new Uint8Array(0);
}
if (this.#buffer.length === 0) {
const buffer = new Uint8Array(n);
const { done, value } = await this.#reader.read(buffer, {
min: buffer.length,
});
if (done) {
throw new Deno.errors.BadResource();
}
return value;
} else {
const remaining = n - this.#buffer.length;
const buffer = new Uint8Array(remaining);
const { value, done } = await this.#reader.read(buffer, {
min: remaining,
});
if (done) {
throw new Deno.errors.BadResource();
}
const result = concateBytes(this.#buffer, value);
this.#buffer = new Uint8Array();
return result;
}
}
#consume(n: number): Uint8Array {
const b = this.#buffer.subarray(0, n);
this.#buffer = this.#buffer.subarray(n);
return b;
}
async #fill() {
const chunk = await this.#reader.read(new Uint8Array(1024));
if (chunk.done) {
throw new Deno.errors.BadResource();
}
const bytes = chunk.value;
this.#buffer = concateBytes(this.#buffer, bytes);
}
}
================================================
FILE: internal/buffered_readable_stream_test.ts
================================================
import { encoder } from "./encoding.ts";
import { assertEquals, assertRejects } from "../deps/std/assert.ts";
import { BufferedReadableStream } from "./buffered_readable_stream.ts";
Deno.test({
name: "BufferedReadableStream",
permissions: "none",
fn: async (t) => {
const decoder = new TextDecoder();
await t.step("readLine", async () => {
const readable = createReadableStreamFromString(
"*2\r\n$5\r\nhello\r\n:1234\r\n",
);
const buffered = new BufferedReadableStream(readable);
assertEquals(decoder.decode(await buffered.readLine()), "*2\r\n");
assertEquals(decoder.decode(await buffered.readLine()), "$5\r\n");
assertEquals(decoder.decode(await buffered.readLine()), "hello\r\n");
assertEquals(decoder.decode(await buffered.readLine()), ":1234\r\n");
await assertRejects(() => buffered.readLine(), Deno.errors.BadResource);
});
await t.step("readN", async () => {
const readable = createReadableStreamFromString(
"$12\r\nhello_world!\r\n",
);
const buffered = new BufferedReadableStream(readable);
await buffered.readN(0);
assertEquals(decoder.decode(await buffered.readLine()), "$12\r\n");
{
const buf = await buffered.readN(5);
assertEquals(decoder.decode(buf), "hello");
}
await buffered.readN(0);
{
const buf = await buffered.readN(7);
assertEquals(decoder.decode(buf), "_world!");
}
await buffered.readN(0);
{
const buf = await buffered.readN(2);
assertEquals(decoder.decode(buf), "\r\n");
}
await buffered.readN(0);
await assertRejects(
() => buffered.readN(1),
Deno.errors.BadResource,
);
});
await t.step(
"`readN` should not throw `RangeError: offset is out of bounds` error",
async () => {
const readable = new ReadableStream({
type: "bytes",
start(controller) {
controller.enqueue(encoder.encode("foobar"));
controller.close();
},
});
const buffered = new BufferedReadableStream(readable);
{
const buf = await buffered.readN(3);
assertEquals(decoder.decode(buf), "foo");
}
{
const buf = await buffered.readN(1);
assertEquals(decoder.decode(buf), "b");
}
await buffered.readN(0);
{
const buf = await buffered.readN(2);
assertEquals(decoder.decode(buf), "ar");
}
},
);
},
});
function createReadableStreamFromString(s: string): ReadableStream<Uint8Array> {
const encoder = new TextEncoder();
let numRead = 0;
return new ReadableStream({
type: "bytes",
pull(controller) {
controller.enqueue(encoder.encode(s[numRead]));
numRead++;
if (numRead >= s.length) {
controller.close();
}
},
});
}
================================================
FILE: internal/concate_bytes.ts
================================================
export function concateBytes(a: Uint8Array, b: Uint8Array) {
if (a.length === 0) return b;
const size = a.length + b.length;
const buf = new Uint8Array(size);
buf.set(a);
buf.set(b, a.length);
return buf;
}
================================================
FILE: internal/concate_bytes_test.ts
================================================
import { assertEquals } from "../deps/std/assert.ts";
import { concateBytes } from "./concate_bytes.ts";
Deno.test("concateBytes", () => {
const encoder = new TextEncoder();
const decoder = new TextDecoder();
const e = (s: string) => encoder.encode(s);
const d = (b: Uint8Array) => decoder.decode(b);
assertEquals(d(concateBytes(e("foo"), e("bar"))), "foobar");
assertEquals(d(concateBytes(e(""), e(""))), "");
assertEquals(d(concateBytes(e(""), e("hello"))), "hello");
});
================================================
FILE: internal/delegate.ts
================================================
type Delegate<TObject, TMethods extends keyof TObject> =
Pick<TObject, TMethods> extends Record<
string | symbol,
// deno-lint-ignore ban-types -- This is used only as a constraint on the type argument
Function
> ? Pick<TObject, TMethods>
: never;
export function delegate<
TObject extends object,
TMethods extends keyof TObject,
>(
target: TObject,
methods: Array<TMethods>,
): Delegate<TObject, TMethods> {
return methods.reduce((proxy, method) => {
if (typeof target[method] === "function") {
proxy[method] = target[method].bind(target);
} else {
throw new Error(`${String(method)} should be a method`);
}
return proxy;
}, {} as Delegate<TObject, TMethods>);
}
================================================
FILE: internal/delegate_test.ts
================================================
import { delegate } from "./delegate.ts";
import {
assert,
assertExists,
assertFalse,
assertNotStrictEquals,
} from "../deps/std/assert.ts";
import type { Has, IsExact, NotHas } from "../deps/std/testing.ts";
import { assertType } from "../deps/std/testing.ts";
Deno.test("delegate", () => {
class Connection {
#isConnected = false;
connect(): void {
this.#isConnected = true;
}
close(): void {
this.#isConnected = false;
}
isConnected(): boolean {
return this.#isConnected;
}
isClosed(): boolean {
return !this.#isConnected;
}
}
const base = new Connection();
const proxy = delegate(base, ["connect", "isConnected"]);
assertNotStrictEquals(proxy.connect, base.connect);
assertNotStrictEquals(proxy.isConnected, base.isConnected);
const kClose = "close";
assert(
// @ts-expect-error - `close()` should not be defined.
proxy[kClose] === undefined,
);
assertExists(base[kClose]);
assertType<Has<Connection, typeof proxy>>(true);
assertType<NotHas<typeof proxy, Connection>>(true);
assertType<IsExact<Connection["connect"], typeof proxy["connect"]>>(true);
assertType<IsExact<Connection["isConnected"], typeof proxy["isConnected"]>>(
true,
);
assertFalse(base.isConnected());
assertFalse(proxy.isConnected());
proxy.connect();
assert(base.isConnected());
assert(proxy.isConnected());
});
================================================
FILE: internal/encoding.ts
================================================
export const encoder = new TextEncoder();
export const decoder = new TextDecoder();
================================================
FILE: internal/on.ts
================================================
interface Options {
signal?: AbortSignal;
}
/**
* Converts {@linkcode EventTarget} to {@linkcode AsyncIterableIterator}, similar to `on()` in `node:events`.
*/
export function on(
eventTarget: EventTarget,
event: string,
options: Options = {},
): AsyncIterableIterator<Event> {
// TODO: Optimize the implementation.
const abortController = new AbortController();
const signal = options.signal
? AbortSignal.any([options.signal, abortController.signal])
: abortController.signal;
const readerQueue: Array<PromiseWithResolvers<Event>> = [];
const bufferedEventQueue: Array<Event> = [];
if (!signal.aborted) {
eventTarget.addEventListener(
event,
(event) => {
if (readerQueue.length) {
const { resolve } = readerQueue.shift()!;
resolve(event);
} else {
bufferedEventQueue.push(event);
}
},
{ signal },
);
}
function cleanup(): void {
for (const d of readerQueue) {
d.reject(signal.reason);
}
readerQueue.length = 0;
}
const iter: AsyncIterableIterator<Event> = {
[Symbol.asyncIterator]() {
return this;
},
async next() {
if (signal.aborted) {
return { done: true, value: undefined };
} else if (bufferedEventQueue.length) {
const event = bufferedEventQueue.shift()!;
return { done: false, value: event };
} else {
const deferred = Promise.withResolvers<Event>();
readerQueue.push(deferred);
const value = await deferred.promise;
return { done: false, value };
}
},
return() {
abortController.abort();
cleanup();
return Promise.resolve({ done: true, value: undefined });
},
};
return iter;
}
================================================
FILE: internal/on_test.ts
================================================
import { on } from "./on.ts";
import {
assert,
assertEquals,
assertStrictEquals,
} from "../deps/std/assert.ts";
Deno.test({
name: "on",
permissions: "none",
fn: async (t) => {
await t.step("implements [Symbol.asyncIterator]", async () => {
const eventType = "foo";
const target = new EventTarget();
const ac = new AbortController();
const iter = on(target, eventType, { signal: ac.signal });
const events: Array<Event> = [];
const promise = (async () => {
for await (const event of iter) {
assertStrictEquals(event.type, eventType);
events.push(event);
if (events.length > 2) {
ac.abort();
}
}
})();
target.dispatchEvent(new CustomEvent(eventType));
target.dispatchEvent(new CustomEvent(eventType + "bar"));
target.dispatchEvent(new CustomEvent(eventType));
target.dispatchEvent(new CustomEvent(eventType));
await promise;
assertEquals(await iter.next(), { done: true, value: undefined });
assertStrictEquals(events.length, 3);
});
await t.step("implements Symbol.asyncIterator#return()", async () => {
const eventType = "bar";
const target = new EventTarget();
const ac = new AbortController();
const iter = on(target, eventType, { signal: ac.signal });
target.dispatchEvent(new CustomEvent(eventType));
const result = await iter.next();
assertStrictEquals(result.done, false);
assertStrictEquals(result.value.type, eventType);
assert(iter.return != null);
iter.return();
assertEquals(await iter.next(), { done: true, value: undefined });
target.dispatchEvent(new CustomEvent(eventType));
assertEquals(await iter.next(), { done: true, value: undefined });
});
},
});
================================================
FILE: internal/symbols.ts
================================================
/**
* @private
*/
export const kUnstableReadReply = Symbol("deno-redis.readReply");
/**
* @private
*/
export const kUnstableWriteCommand = Symbol("deno-redis.writeCommand");
/**
* @private
*/
export const kUnstablePipeline = Symbol("deno-redis.pipeline");
/**
* @private
*/
export const kUnstableCreateProtocol = Symbol("deno-redis.createProtocol");
/**
* @private
*/
export const kUnstableProtover = Symbol("deno-redis.protover");
/**
* @private
*/
export const kUnstableStartReadLoop = Symbol("deno-redis.startReadLoop");
================================================
FILE: internal/typed_event_target.ts
================================================
export interface TypedEventTarget<TEventMap extends Record<string, unknown>>
extends
Omit<
EventTarget,
"addEventListener" | "removeEventListener" | "dispatchEvent"
> {
addEventListener<K extends keyof TEventMap>(
type: K,
callback: (
event: CustomEvent<TEventMap[K]>,
) => void,
options?: AddEventListenerOptions | boolean,
): void;
removeEventListener<K extends keyof TEventMap>(
type: K,
callback: (
event: CustomEvent<TEventMap[K]>,
) => void,
options?: EventListenerOptions | boolean,
): void;
}
export function createTypedEventTarget<
TEventMap extends Record<string, unknown>,
>(): TypedEventTarget<TEventMap> {
return new EventTarget() as TypedEventTarget<TEventMap>;
}
export function dispatchEvent<
TEventMap extends Record<string, unknown>,
TKey extends Extract<keyof TEventMap, string>,
>(
eventTarget: TypedEventTarget<TEventMap>,
event: TKey,
detail: TEventMap[TKey],
): boolean {
return (eventTarget as EventTarget).dispatchEvent(
new CustomEvent(event, {
detail,
}),
);
}
================================================
FILE: mod.ts
================================================
// Generated by tools/make_mod.ts. Don't edit.
export { okReply } from "./protocol/shared/types.ts";
export { connect, create, createLazyClient, parseURL } from "./redis.ts";
export {
ConnectionClosedError,
EOFError,
ErrorReplyError,
InvalidStateError,
NotImplementedError,
SubscriptionClosedError,
} from "./errors.ts";
export type { Backoff, ExponentialBackoffOptions } from "./backoff.ts";
export type {
ACLLogMode,
BitfieldOpts,
BitfieldWithOverflowOpts,
ClientCachingMode,
ClientKillOpts,
ClientListOpts,
ClientPauseMode,
ClientTrackingOpts,
ClientType,
ClientUnblockingBehaviour,
ClusterFailoverMode,
ClusterResetMode,
ClusterSetSlotSubcommand,
GeoRadiusOpts,
GeoUnit,
HelloOpts,
HScanOpts,
LInsertLocation,
LPosOpts,
LPosWithCountOpts,
MemoryUsageOpts,
MigrateOpts,
RedisCommands,
RestoreOpts,
ScanOpts,
ScriptDebugMode,
SetOpts,
SetReply,
SetWithModeOpts,
ShutdownMode,
SortOpts,
SortWithDestinationOpts,
SScanOpts,
StralgoAlgorithm,
StralgoOpts,
StralgoTarget,
ZAddOpts,
ZAddReply,
ZInterOpts,
ZInterstoreOpts,
ZRangeByLexOpts,
ZRangeByScoreOpts,
ZRangeOpts,
ZScanOpts,
ZUnionstoreOpts,
} from "./command.ts";
export type {
Connection,
RedisConnectionOptions,
SendCommandOptions,
} from "./connection.ts";
export type {
ConnectionErrorEventDetails,
ConnectionEvent,
ConnectionEventType,
ConnectionReconnectingEventDetails,
} from "./events.ts";
export type { Client } from "./client.ts";
export type { RedisPubSubMessage, RedisSubscription } from "./subscription.ts";
export type { CommandExecutor } from "./executor.ts";
export type { RedisPipeline } from "./pipeline.ts";
export type {
Binary,
Bulk,
BulkNil,
BulkString,
ConditionalArray,
Integer,
Raw,
RawOrError,
RedisReply,
RedisValue,
SimpleString,
} from "./protocol/shared/types.ts";
export type { Redis, RedisConnectOptions } from "./redis.ts";
export type {
StartEndCount,
XAddFieldValues,
XClaimJustXId,
XClaimMessages,
XClaimOpts,
XClaimReply,
XConsumerDetail,
XGroupDetail,
XId,
XIdAdd,
XIdCreateGroup,
XIdGroupRead,
XIdInput,
XIdNeg,
XIdPos,
XInfoConsumer,
XInfoConsumersReply,
XInfoGroup,
XInfoGroupsReply,
XInfoStreamFullReply,
XInfoStreamReply,
XKeyId,
XKeyIdGroup,
XKeyIdGroupLike,
XKeyIdLike,
XMaxlen,
XMessage,
XPendingConsumer,
XPendingCount,
XPendingReply,
XReadGroupOpts,
XReadIdData,
XReadOpts,
XReadReply,
XReadReplyRaw,
XReadStream,
XReadStreamRaw,
} from "./stream.ts";
================================================
FILE: pipeline.ts
================================================
import type { Connection, SendCommandOptions } from "./connection.ts";
import { kEmptyRedisArgs } from "./protocol/shared/command.ts";
import type { Client } from "./client.ts";
import type {
DefaultPubSubMessageType,
PubSubMessageType,
RedisSubscription,
SubscribeCommand,
} from "./subscription.ts";
import type {
RawOrError,
RedisReply,
RedisValue,
} from "./protocol/shared/types.ts";
import { okReply } from "./protocol/shared/types.ts";
import type { Redis } from "./redis.ts";
import { create } from "./redis.ts";
import { kUnstablePipeline } from "./internal/symbols.ts";
export interface RedisPipeline extends Redis {
flush(): Promise<RawOrError[]>;
}
export function createRedisPipeline(
connection: Connection,
tx = false,
): RedisPipeline {
const pipeline = new PipelineClient(connection, tx);
function flush(): Promise<RawOrError[]> {
return pipeline.flush();
}
const client = create(pipeline);
return Object.assign(client, { flush });
}
class PipelineClient implements Client {
private commands: {
command: string;
args: RedisValue[];
returnUint8Arrays?: boolean;
}[] = [];
private queue: {
commands: {
command: string;
args: RedisValue[];
returnUint8Arrays?: boolean;
}[];
resolve: (value: RawOrError[]) => void;
reject: (error: unknown) => void;
}[] = [];
constructor(
readonly connection: Connection,
private tx: boolean,
) {
}
exec(
command: string,
...args: RedisValue[]
): Promise<RedisReply> {
return this.sendCommand(command, args);
}
sendCommand(
command: string,
args?: RedisValue[],
options?: SendCommandOptions,
): Promise<RedisReply> {
this.commands.push({
command,
args: args ?? kEmptyRedisArgs,
returnUint8Arrays: options?.returnUint8Arrays,
});
return Promise.resolve(okReply);
}
close(): void {
return this.connection.close();
}
subscribe<TMessage extends PubSubMessageType = DefaultPubSubMessageType>(
_command: SubscribeCommand,
..._channelsOrPatterns: Array<string>
): Promise<RedisSubscription<TMessage>> {
return Promise.reject(new Error("Pub/Sub cannot be used with a pipeline"));
}
flush(): Promise<RawOrError[]> {
if (this.tx) {
this.commands.unshift({ command: "MULTI", args: [] });
this.commands.push({ command: "EXEC", args: [] });
}
const { promise, resolve, reject } = Promise.withResolvers<RawOrError[]>();
this.queue.push({ commands: [...this.commands], resolve, reject });
if (this.queue.length === 1) {
this.dequeue();
}
this.commands = [];
return promise;
}
private dequeue(): void {
const [e] = this.queue;
if (!e) return;
this.connection[kUnstablePipeline](e.commands)
.then(e.resolve)
.catch(e.reject)
.finally(() => {
this.queue.shift();
this.dequeue();
});
}
}
================================================
FILE: pool/client.ts
================================================
import type { Connection, SendCommandOptions } from "../connection.ts";
import type { Pool } from "./pool.ts";
import type { Client } from "../client.ts";
import type {
DefaultPubSubMessageType,
PubSubMessageType,
RedisSubscription,
SubscribeCommand,
} from "../subscription.ts";
import { createDefaultClient } from "../default_client.ts";
import {
kUnstablePipeline,
kUnstableReadReply,
kUnstableStartReadLoop,
kUnstableWriteCommand,
} from "../internal/symbols.ts";
import { delegate } from "../internal/delegate.ts";
import type { RedisReply, RedisValue } from "../protocol/shared/types.ts";
export function createPoolClient(pool: Pool<Connection>): Client {
return new PoolClient(pool);
}
class PoolClient implements Client {
readonly #pool: Pool<Connection>;
constructor(pool: Pool<Connection>) {
this.#pool = pool;
}
get connection(): Connection {
throw new Error("PoolClient.connection is not supported");
}
async exec(
command: string,
...args: RedisValue[]
): Promise<RedisReply> {
const connection = await this.#pool.acquire();
try {
const client = createDefaultClient(connection);
return await client.exec(command, ...args);
} finally {
this.#pool.release(connection);
}
}
async sendCommand(
command: string,
args?: RedisValue[],
options?: SendCommandOptions,
): Promise<RedisReply> {
const connection = await this.#pool.acquire();
try {
const client = createDefaultClient(connection);
return await client.sendCommand(command, args, options);
} finally {
this.#pool.release(connection);
}
}
async subscribe<
TMessage extends PubSubMessageType = DefaultPubSubMessageType,
>(
command: SubscribeCommand,
...channelsOrPatterns: Array<string>
): Promise<RedisSubscription<TMessage>> {
const connection = await this.#pool.acquire();
const client = createDefaultClient(
createPoolConnection(this.#pool, connection),
);
try {
const subscription = await client.subscribe<TMessage>(
command,
...channelsOrPatterns,
);
return subscription;
} catch (error) {
this.#pool.release(connection);
throw error;
}
}
close(): void {
return this.#pool.close();
}
}
function createPoolConnection(
pool: Pool<Connection>,
connection: Connection,
): Connection {
function close(): void {
return pool.release(connection);
}
return {
...delegate(connection, [
"connect",
"reconnect",
"sendCommand",
"addEventListener",
"removeEventListener",
Symbol.dispose,
kUnstableReadReply,
kUnstableWriteCommand,
kUnstablePipeline,
kUnstableStartReadLoop,
]),
close,
get name() {
return connection.name;
},
get isConnected() {
return connection.isConnected;
},
get isClosed() {
return connection.isClosed;
},
};
}
================================================
FILE: pool/default_pool.ts
================================================
import type { Pool } from "./pool.ts";
class AlreadyRemovedFromPoolError extends Error {
constructor() {
super("This connection has already been removed from the pool.");
}
}
const kDefaultTimeout = 5_000;
class DefaultPool<T extends Disposable> implements Pool<T> {
readonly #idle: Array<T> = [];
readonly #connections: Array<T> = [];
#connectionCount: number = 0;
readonly #deferredQueue: Array<PromiseWithResolvers<T>> = [];
readonly #options: Required<PoolOptions<T>>;
constructor(
{
maxConnections = 8,
acquire,
}: PoolOptions<T>,
) {
this.#options = {
acquire,
maxConnections,
};
}
async acquire(signal?: AbortSignal): Promise<T> {
signal ||= AbortSignal.timeout(kDefaultTimeout);
signal.throwIfAborted();
if (this.#idle.length > 0) {
const conn = this.#idle.shift()!;
return Promise.resolve(conn);
}
if (this.#connectionCount < this.#options.maxConnections) {
this.#connectionCount++;
try {
const connection = await this.#options.acquire();
this.#connections.push(connection);
return connection;
} catch (error) {
this.#connectionCount--;
throw error;
}
}
const deferred = Promise.withResolvers<T>();
this.#deferredQueue.push(deferred);
const { promise, reject } = deferred;
const onAbort = () => {
const i = this.#deferredQueue.indexOf(deferred);
if (i === -1) return;
this.#deferredQueue.splice(i, 1);
reject(signal.reason);
};
signal.addEventListener("abort", onAbort, { once: true });
return promise;
}
#has(conn: T): boolean {
return this.#connections.includes(conn);
}
release(conn: T): void {
if (!this.#has(conn)) {
throw new AlreadyRemovedFromPoolError();
} else if (this.#deferredQueue.length > 0) {
const i = this.#deferredQueue.shift()!;
i.resolve(conn);
} else {
this.#idle.push(conn);
}
}
close() {
const errors: Array<unknown> = [];
for (const x of [...this.#connections]) {
try {
x[Symbol.dispose]();
} catch (error) {
errors.push(error);
}
}
this.#connections.length = 0;
this.#idle.length = 0;
if (errors.length > 0) {
throw new AggregateError(errors);
}
}
}
export interface PoolOptions<T extends Disposable> {
maxConnections?: number;
acquire(): Promise<T>;
}
export function createDefaultPool<T extends Disposable>(
options: PoolOptions<T>,
): Pool<T> {
return new DefaultPool<T>(options);
}
================================================
FILE: pool/default_pool_test.ts
================================================
import { assert, assertEquals, assertRejects } from "../deps/std/assert.ts";
import { createDefaultPool } from "./default_pool.ts";
class FakeConnection implements Disposable {
#isClosed = false;
isClosed() {
return this.#isClosed;
}
[Symbol.dispose]() {
if (this.#isClosed) {
throw new Error("Already closed");
}
this.#isClosed = true;
}
}
Deno.test({
name: "DefaultPool",
permissions: "none",
fn: async () => {
const openConnections: Array<FakeConnection> = [];
const pool = createDefaultPool({
acquire: () => {
const connection = new FakeConnection();
openConnections.push(connection);
return Promise.resolve(connection);
},
maxConnections: 2,
});
assertEquals(openConnections, []);
const signal = AbortSignal.timeout(200);
const conn1 = await pool.acquire(signal);
assertEquals(openConnections, [conn1]);
assert(openConnections.every((x) => !x.isClosed()));
assert(!signal.aborted);
const conn2 = await pool.acquire(signal);
assertEquals(openConnections, [conn1, conn2]);
assert(!conn2.isClosed());
assert(openConnections.every((x) => !x.isClosed()));
assert(!signal.aborted);
{
// Tests timeout handling
await assertRejects(
() => pool.acquire(signal),
"Intentionally aborted",
);
assert(signal.aborted);
assertEquals(openConnections, [conn1, conn2]);
assert(openConnections.every((x) => !x.isClosed()));
}
{
// Tests `release()`
pool.release(conn2);
assertEquals(openConnections, [conn1, conn2]);
const conn = await pool.acquire(new AbortController().signal);
assert(conn === conn2, "A new connection should not be created");
assertEquals(openConnections, [conn1, conn2]);
}
{
// `Pool#acquire` should wait for an active connection to be released.
const signal = AbortSignal.timeout(3_000);
const promise = pool.acquire(signal);
setTimeout(() => {
pool.release(conn1);
}, 50);
const conn = await promise;
assert(conn === conn1, "A new connection should not be created");
assertEquals(openConnections, [conn1, conn2]);
assert(!signal.aborted);
}
{
// `Pool#close` closes all connections
pool.close();
assert(openConnections.every((x) => x.isClosed()));
}
},
});
================================================
FILE: pool/mod.ts
================================================
import type { Redis, RedisConnectOptions } from "../redis.ts";
import { create } from "../redis.ts";
import type { Connection } from "../connection.ts";
import { createRedisConnection } from "../default_connection.ts";
import { createDefaultPool } from "./default_pool.ts";
import { createPoolClient as baseCreatePoolClient } from "./client.ts";
export interface CreatePoolClientOptions {
connection: RedisConnectOptions;
maxConnections?: number;
}
export function createPoolClient(
options: CreatePoolClientOptions,
): Promise<Redis> {
const pool = createDefaultPool<Connection>({
acquire,
maxConnections: options.maxConnections ?? 8,
});
const client = create(baseCreatePoolClient(pool));
return Promise.resolve(client);
async function acquire(): Promise<Connection> {
const { hostname, port, ...connectionOptions } = options.connection;
const connection = createRedisConnection(hostname, port, connectionOptions);
await connection.connect();
return connection;
}
}
================================================
FILE: pool/pool.ts
================================================
export interface Pool<T extends Disposable> {
acquire(signal?: AbortSignal): Promise<T>;
release(item: T): void;
close(): void;
}
================================================
FILE: protocol/deno_streams/command.ts
================================================
import type { BufReader, BufWriter } from "../../deps/std/io.ts";
import { readReply } from "./reply.ts";
import { ErrorReplyError } from "../../errors.ts";
import type { RedisReply, RedisValue } from "../shared/types.ts";
import { encodeCommand } from "../shared/command.ts";
import type { Command } from "../shared/protocol.ts";
export async function writeCommand(
writer: BufWriter,
command: string,
args: RedisValue[],
) {
const request = encodeCommand(command, args);
await writer.write(request);
}
export async function sendCommand(
writer: BufWriter,
reader: BufReader,
command: string,
args: RedisValue[],
returnUint8Arrays?: boolean,
): Promise<RedisReply> {
await writeCommand(writer, command, args);
await writer.flush();
return readReply(reader, returnUint8Arrays);
}
export async function sendCommands(
writer: BufWriter,
reader: BufReader,
commands: Command[],
): Promise<(RedisReply | ErrorReplyError)[]> {
for (const { command, args } of commands) {
await writeCommand(writer, command, args);
}
await writer.flush();
const ret: (RedisReply | ErrorReplyError)[] = [];
for (let i = 0; i < commands.length; i++) {
try {
const rep = await readReply(reader, commands[i].returnUint8Arrays);
ret.push(rep);
} catch (e) {
if (e instanceof ErrorReplyError) {
ret.push(e);
} else {
throw e;
}
}
}
return ret;
}
================================================
FILE: protocol/deno_streams/mod.ts
================================================
import { BufReader, BufWriter } from "../../deps/std/io.ts";
import { readReply } from "./reply.ts";
import { sendCommand, sendCommands, writeCommand } from "./command.ts";
import type { Command, Protocol as BaseProtocol } from "../shared/protocol.ts";
import type { RedisReply, RedisValue } from "../shared/types.ts";
import type { ErrorReplyError } from "../../errors.ts";
export class Protocol implements BaseProtocol {
#reader: BufReader;
#writer: BufWriter;
constructor(conn: Deno.Conn) {
this.#reader = new BufReader(conn);
this.#writer = new BufWriter(conn);
}
sendCommand(
command: string,
args: RedisValue[],
returnsUint8Arrays?: boolean | undefined,
): Promise<RedisReply> {
return sendCommand(
this.#writer,
this.#reader,
command,
args,
returnsUint8Arrays,
);
}
readReply(returnsUint8Arrays?: boolean): Promise<RedisReply> {
return readReply(this.#reader, returnsUint8Arrays);
}
async writeCommand(command: Command): Promise<void> {
await writeCommand(this.#writer, command.command, command.args);
await this.#writer.flush();
}
pipeline(commands: Command[]): Promise<Array<RedisReply | ErrorReplyError>> {
return sendCommands(this.#writer, this.#reader, commands);
}
}
================================================
FILE: protocol/deno_streams/reply.ts
================================================
import type { BufReader } from "../../deps/std/io.ts";
import type * as types from "../shared/types.ts";
import {
ArrayReplyCode,
AttributeReplyCode,
BigNumberReplyCode,
BlobErrorReplyCode,
BooleanReplyCode,
BulkReplyCode,
DoubleReplyCode,
ErrorReplyCode,
IntegerReplyCode,
MapReplyCode,
NullReplyCode,
PushReplyCode,
SetReplyCode,
SimpleStringCode,
VerbatimStringCode,
} from "../shared/reply.ts";
import { EOFError, ErrorReplyError, InvalidStateError } from "../../errors.ts";
import { decoder } from "../../internal/encoding.ts";
export async function readReply(
reader: BufReader,
returnUint8Arrays?: boolean,
): Promise<types.RedisReply> {
const res = await reader.peek(1);
if (res == null) {
throw new EOFError();
}
const code = res[0];
if (code === ErrorReplyCode) {
await readErrorReplyOrFail(reader);
}
switch (code) {
case IntegerReplyCode:
return readIntegerReply(reader);
case SimpleStringCode:
return readSimpleStringReply(reader, returnUint8Arrays);
case BulkReplyCode:
return readBulkReply(reader, returnUint8Arrays);
case ArrayReplyCode:
return readArrayReply(reader, returnUint8Arrays);
case MapReplyCode:
return readMapReply(reader, returnUint8Arrays);
case SetReplyCode:
return readSetReply(reader, returnUint8Arrays);
case BooleanReplyCode:
return readBooleanReply(reader);
case DoubleReplyCode:
return readDoubleReply(reader, returnUint8Arrays);
case BigNumberReplyCode:
return readBigNumberReply(reader, returnUint8Arrays);
case VerbatimStringCode:
return readVerbatimStringReply(reader, returnUint8Arrays);
case NullReplyCode:
return readNullReply(reader);
case PushReplyCode:
return readPushReply(reader, returnUint8Arrays);
case AttributeReplyCode: {
await readAttributeReply(reader);
return readReply(reader, returnUint8Arrays);
}
case BlobErrorReplyCode: {
const body = (await readBlobReply(reader, BlobErrorReplyCode)) as string;
throw new ErrorReplyError(body);
}
default:
throw new InvalidStateError(
`unknown code: '${String.fromCharCode(code)}' (${code})`,
);
}
}
async function readIntegerReply(
reader: BufReader,
): Promise<number> {
const line = await readLine(reader);
if (line == null) {
throw new InvalidStateError();
}
return Number.parseInt(decoder.decode(line.subarray(1, line.length)));
}
function readBulkReply(
reader: BufReader,
returnUint8Arrays?: boolean,
): Promise<BlobLikeReply> {
return readBlobReply(reader, BulkReplyCode, returnUint8Arrays);
}
function readVerbatimStringReply(
reader: BufReader,
returnUint8Arrays?: boolean,
): Promise<BlobLikeReply> {
return readBlobReply(reader, VerbatimStringCode, returnUint8Arrays);
}
function readSimpleStringReply(
reader: BufReader,
returnUint8Arrays?: boolean,
): Promise<string | types.Binary> {
return readSingleLineReply(reader, SimpleStringCode, returnUint8Arrays);
}
function readArrayReply(
reader: BufReader,
returnUint8Arrays?: boolean,
): Promise<Array<types.RedisReply> | null> {
return readArrayLikeReply(reader, returnUint8Arrays);
}
function readPushReply(
reader: BufReader,
returnUint8Arrays?: boolean,
): Promise<Array<types.RedisReply> | null> {
return readArrayLikeReply(reader, returnUint8Arrays);
}
async function readArrayLikeReply(
reader: BufReader,
returnUint8Arrays?: boolean,
): Promise<Array<types.RedisReply> | null> {
const line = await readLine(reader);
if (line == null) {
throw new InvalidStateError();
}
const argCount = parseSize(line);
if (argCount === -1) {
// `-1` indicates a null array
return null;
}
const array: Array<types.RedisReply> = [];
for (let i = 0; i < argCount; i++) {
array.push(await readReply(reader, returnUint8Arrays));
}
return array;
}
/**
* NOTE: We treat a set type as an array to keep backward compatibility.
*/
async function readSetReply(
reader: BufReader,
returnUint8Arrays?: boolean,
): Promise<Array<types.RedisReply> | null> {
const line = await readLine(reader);
if (line == null) {
throw new InvalidStateError();
}
const size = parseSize(line);
if (size === -1) {
// `-1` indicates a null set
return null;
}
const set: Array<types.RedisReply> = [];
for (let i = 0; i < size; i++) {
set.push(await readReply(reader, returnUint8Arrays));
}
return set;
}
/**
* NOTE: We treat a map type as an array to keep backward compatibility.
*/
async function readMapReply(
reader: BufReader,
returnUint8Arrays?: boolean,
): Promise<Array<types.RedisReply> | null> {
const line = await readLine(reader);
if (line == null) {
throw new InvalidStateError();
}
const numberOfFieldValuePairs = parseSize(line);
if (numberOfFieldValuePairs === -1) {
return null;
}
const entries: Array<types.RedisReply> = [];
for (let i = 0; i < (numberOfFieldValuePairs * 2); i++) {
entries.push(await readReply(reader, returnUint8Arrays));
}
return entries;
}
/**
* NOTE: Currently, we simply drop attributes.
* TODO: Provide a way for users to capture attributes.
*/
async function readAttributeReply(
reader: BufReader,
): Promise<void> {
const line = await readLine(reader);
if (line == null) {
throw new InvalidStateError();
}
const numberOfAttributes = parseSize(line);
if (numberOfAttributes === -1) {
return;
}
for (let i = 0; i < numberOfAttributes; i++) {
await readReply(reader); // Reads a key
await readReply(reader); // Raads a value
}
}
async function readBooleanReply(reader: BufReader): Promise<1 | 0> {
const line = await readLine(reader);
if (line == null) {
throw new InvalidStateError();
}
const isTrue = line[1] === 116;
return isTrue
? 1 // `#t`
: 0; // `#f`
}
function readDoubleReply(
reader: BufReader,
returnUint8Arrays?: boolean,
): Promise<string | types.Binary> {
return readSingleLineReply(reader, DoubleReplyCode, returnUint8Arrays);
}
function readBigNumberReply(
reader: BufReader,
returnUint8Arrays?: boolean,
): Promise<string | types.Binary> {
return readSingleLineReply(reader, BigNumberReplyCode, returnUint8Arrays);
}
async function readNullReply(reader: BufReader): Promise<null> {
// Discards the current line
await readLine(reader);
return null;
}
async function readSingleLineReply(
reader: BufReader,
expectedCode: number,
returnUint8Arrays?: boolean,
): Promise<string | types.Binary> {
const line = await readLine(reader);
if (line == null) {
throw new InvalidStateError();
}
if (line[0] !== expectedCode) {
parseErrorReplyOrFail(line);
}
const body = line.subarray(1);
return returnUint8Arrays ? body : decoder.decode(body);
}
type BlobLikeReply = string | types.Binary | null;
async function readBlobReply(
reader: BufReader,
expectedCode: number,
returnUint8Arrays?: boolean,
): Promise<BlobLikeReply> {
const line = await readLine(reader);
if (line == null) {
throw new InvalidStateError();
}
if (line[0] !== expectedCode) {
parseErrorReplyOrFail(line);
}
const size = parseSize(line);
if (size < 0) {
// nil bulk reply
return null;
}
const dest = new Uint8Array(size + 2);
await reader.readFull(dest);
const body = dest.subarray(0, dest.length - 2); // Strip CR and LF
return returnUint8Arrays ? body : decoder.decode(body);
}
function parseErrorReplyOrFail(line: Uint8Array): never {
const code = line[0];
if (code === ErrorReplyCode) {
throw new ErrorReplyError(decoder.decode(line));
}
throw new Error(`invalid line: ${line}`);
}
async function readErrorReplyOrFail(reader: BufReader): Promise<never> {
const line = await readLine(reader);
if (line == null) {
throw new InvalidStateError();
}
parseErrorReplyOrFail(line);
}
async function readLine(reader: BufReader): Promise<Uint8Array> {
let result = await reader.readLine();
if (result == null) {
throw new InvalidStateError();
}
let line = result.line;
while (result?.more) {
result = await reader.readLine();
if (result == null) {
throw new InvalidStateError();
}
const mergedLine = new Uint8Array(line.length + result.line.length);
mergedLine.set(line);
mergedLine.set(result.line, line.length);
line = mergedLine;
}
return line;
}
function parseSize(line: Uint8Array): number {
const sizeStr = line.subarray(1, line.length);
const size = parseInt(decoder.decode(sizeStr));
return size;
}
================================================
FILE: protocol/deno_streams/reply_test.ts
================================================
import { assertEquals, assertRejects } from "../../deps/std/assert.ts";
import { BufReader, readerFromStreamReader } from "../../deps/std/io.ts";
import { ErrorReplyError } from "../../errors.ts";
import { readReply } from "./reply.ts";
import type { RedisReply } from "../shared/types.ts";
Deno.test({
name: "readReply",
permissions: "none",
fn: async (t) => {
await t.step("array", async () => {
const encoder = new TextEncoder();
const rawReply = "*4\r\n$3\r\nfoo\r\n_\r\n(123456789\r\n:456\r\n";
const readable = ReadableStream.from([encoder.encode(rawReply)]);
const reader = readable.getReader();
const reply = await readReply(
BufReader.create(readerFromStreamReader(reader)),
);
assertEquals(reply, ["foo", null, "123456789", 456]);
});
await t.step("blob error", async () => {
const encoder = new TextEncoder();
const rawReply = "!21\r\nSYNTAX invalid syntax\r\n_\r\n";
const readable = ReadableStream.from([encoder.encode(rawReply)]);
const reader = readable.getReader();
const buf = BufReader.create(readerFromStreamReader(reader));
await assertRejects(
() => readReply(buf),
ErrorReplyError,
"SYNTAX invalid syntax",
);
assertEquals(await readReply(buf), null);
});
await t.step("attribute", async () => {
const encoder = new TextEncoder();
const cases: Array<[given: string, expected: RedisReply]> = [
[
"|1\r\n+foo\r\n%2\r\n$3\r\nbar\r\n:123\r\n$1\r\na\r\n,1.23\r\n+Hello World\r\n",
"Hello World",
],
[
"*3\r\n:123\r\n|2\r\n+foo\r\n:1\r\n+bar\r\n:45\r\n+str\r\n,1.23\r\n",
[123, "str", "1.23"],
],
];
for (
const [given, expected] of cases
) {
const readable = ReadableStream.from([encoder.encode(given)]);
const reader = readable.getReader();
const buf = BufReader.create(readerFromStreamReader(reader));
const actual = await readReply(buf);
assertEquals(actual, expected);
}
});
},
});
================================================
FILE: protocol/shared/command.ts
================================================
import { concat } from "../../deps/std/bytes.ts";
import { encoder } from "../../internal/encoding.ts";
import type { RedisValue } from "./types.ts";
import type { Command } from "./protocol.ts";
const CRLF = encoder.encode("\r\n");
const ArrayCode = encoder.encode("*");
const BulkCode = encoder.encode("$");
const kEmptyBuffer = new Uint8Array(0);
export const kEmptyRedisArgs: Array<RedisValue> = [];
export function encodeCommand(
command: string,
args: RedisValue[],
): Uint8Array {
const encodedArgsCount = encoder.encode(
String(1 + args.length),
);
const encodedCommand = encoder.encode(command);
const encodedCommandLength = encoder.encode(
String(encodedCommand.byteLength),
);
let totalBytes = ArrayCode.byteLength +
encodedArgsCount.byteLength +
CRLF.byteLength +
BulkCode.byteLength +
encodedCommandLength.byteLength +
CRLF.byteLength +
encodedCommand.byteLength +
CRLF.byteLength;
const encodedArgs: Array<Uint8Array> = Array(args.length);
for (let i = 0; i < args.length; i++) {
const arg = args[i];
const bytes = arg instanceof Uint8Array
? arg
: (arg == null ? kEmptyBuffer : encoder.encode(String(arg)));
const bytesLen = bytes.byteLength;
totalBytes += BulkCode.byteLength +
String(bytesLen).length +
CRLF.byteLength +
bytes.byteLength +
CRLF.byteLength;
encodedArgs[i] = bytes;
}
const request = new Uint8Array(totalBytes);
let index = 0;
index = writeFrom(request, ArrayCode, index);
index = writeFrom(request, encodedArgsCount, index);
index = writeFrom(request, CRLF, index);
index = writeFrom(request, BulkCode, index);
index = writeFrom(request, encodedCommandLength, index);
index = writeFrom(request, CRLF, index);
index = writeFrom(request, encodedCommand, index);
index = writeFrom(request, CRLF, index);
for (let i = 0; i < encodedArgs.length; i++) {
const encodedArg = encodedArgs[i];
const encodedArgLength = encoder.encode(String(encodedArg.byteLength));
index = writeFrom(request, BulkCode, index);
index = writeFrom(request, encodedArgLength, index);
index = writeFrom(request, CRLF, index);
index = writeFrom(request, encodedArg, index);
index = writeFrom(request, CRLF, index);
}
return request;
}
function writeFrom(
bytes: Uint8Array,
payload: Uint8Array,
fromIndex: number,
): number {
bytes.set(payload, fromIndex);
return fromIndex + payload.byteLength;
}
export function encodeCommands(commands: Array<Command>): Uint8Array {
// TODO: find a more optimized solution.
const bufs: Array<Uint8Array> = Array(commands.length);
for (let i = 0; i < commands.length; i++) {
const { command, args } = commands[i];
bufs[i] = encodeCommand(command, args);
}
return concat(bufs);
}
================================================
FILE: protocol/shared/command_test.ts
================================================
import { encodeCommand } from "./command.ts";
import { assertEquals } from "../../deps/std/assert.ts";
Deno.test({
name: "encodeCommand",
permissions: "none",
fn: () => {
const actual = encodeCommand("SET", ["name", "bar"]);
const expected = new TextEncoder().encode(
"*3\r\n$3\r\nSET\r\n$4\r\nname\r\n$3\r\nbar\r\n",
);
assertEquals(actual, expected);
},
});
================================================
FILE: protocol/shared/protocol.ts
================================================
import type { RedisReply, RedisValue } from "./types.ts";
import type { ErrorReplyError } from "../../errors.ts";
export interface Command {
command: string;
args: RedisValue[];
returnUint8Arrays?: boolean;
}
export interface Protocol {
sendCommand(
command: string,
args: Array<RedisValue>,
returnsUint8Arrays?: boolean,
): Promise<RedisReply>;
readReply(returnsUint8Array?: boolean): Promise<RedisReply>;
writeCommand(command: Command): Promise<void>;
pipeline(
commands: Array<Command>,
): Promise<Array<RedisReply | ErrorReplyError>>;
}
================================================
FILE: protocol/shared/reply.ts
================================================
/**
* Represents a number in RESP2/RESP3.
*/
export const IntegerReplyCode = ":".charCodeAt(0);
/**
* Represents a double which is introduced in RESP3.
*/
export const DoubleReplyCode = ",".charCodeAt(0);
/**
* Represents a blob string in RESP2/RESP3.
*/
export const BulkReplyCode = "$".charCodeAt(0);
export const SimpleStringCode = "+".charCodeAt(0);
/** Represents a verbatim string in RESP3. */
export const VerbatimStringCode = "=".charCodeAt(0);
export const ArrayReplyCode = "*".charCodeAt(0);
/**
* Represents a simple error in RESP2/RESP3.
*/
export const ErrorReplyCode = "-".charCodeAt(0);
/** Represents a blob error which is introduced in RESP3. */
export const BlobErrorReplyCode = "!".charCodeAt(0);
/** Represents a map which is introduced in RESP3. */
export const MapReplyCode = "%".charCodeAt(0);
/** Represents a set which is introduced in RESP3. */
export const SetReplyCode = "~".charCodeAt(0);
/** Represents a boolean which is introduced in RESP3. */
export const BooleanReplyCode = "#".charCodeAt(0);
/** Represents a big number which is introduced in RESP3. */
export const BigNumberReplyCode = "(".charCodeAt(0);
/** Represents the null type which is introduced in RESP3. */
export const NullReplyCode = "_".charCodeAt(0);
/** Represents the attribute type which is introduced in RESP3. */
export const AttributeReplyCode = "|".charCodeAt(0);
/** Represents the push type which is introduced in RESP3. */
export const PushReplyCode = ">".charCodeAt(0);
================================================
FILE: protocol/shared/types.ts
================================================
import type { ErrorReplyError } from "../../errors.ts";
/**
* @see https://redis.io/topics/protocol
*/
export type RedisValue = string | number | Uint8Array;
/**
* @description Represents the type of the value returned by `SimpleStringReply#value()`.
*/
export type SimpleString = string;
/**
* @description Represents the type of the value returned by `IntegerReply#value()`.
*/
export type Integer = number;
/**
* @description Represents the type of the value returned by `BulkReply#value()`.
*/
export type Bulk = BulkString | BulkNil;
/**
* @description Represents the **bulk string** type in the RESP2 protocol.
*/
export type BulkString = string;
/**
* @description Represents the **null bulk string** and **null array** in the RESP2 protocol.
*/
export type BulkNil = null;
/**
* @description Represents the some type in the RESP2 protocol.
*/
export type Raw = SimpleString | Integer | Bulk | ConditionalArray | Binary;
export type Binary = Uint8Array;
/**
* @description Represents the type of the value returned by `ArrayReply#value()`.
*/
export type ConditionalArray = Raw[];
export type RedisReply = Raw | ConditionalArray;
export type RawOrError = Raw | ErrorReplyError;
export const okReply = "OK";
export type Protover = 2 | 3;
================================================
FILE: protocol/web_streams/command.ts
================================================
import { readReply } from "./reply.ts";
import { ErrorReplyError } from "../../errors.ts";
import type { BufferedReadableStream } from "../../internal/buffered_readable_stream.ts";
import type { RedisReply, RedisValue } from "../shared/types.ts";
import { encodeCommand, encodeCommands } from "../shared/command.ts";
export async function writeCommand(
writable: WritableStream<Uint8Array>,
command: string,
args: RedisValue[],
) {
const request = encodeCommand(command, args);
const writer = writable.getWriter();
try {
await writer.write(request);
} finally {
writer.releaseLock();
}
}
export async function sendCommand(
writable: WritableStream<Uint8Array>,
readable: BufferedReadableStream,
command: string,
args: RedisValue[],
returnUint8Arrays?: boolean,
): Promise<RedisReply> {
await writeCommand(writable, command, args);
return readReply(readable, returnUint8Arrays);
}
export interface Command {
command: string;
args: RedisValue[];
returnUint8Arrays?: boolean;
}
export async function sendCommands(
writable: WritableStream<Uint8Array>,
readable: BufferedReadableStream,
commands: Command[],
): Promise<(RedisReply | ErrorReplyError)[]> {
const request = encodeCommands(commands);
const writer = writable.getWriter();
try {
await writer.write(request);
} finally {
writer.releaseLock();
}
const ret: (RedisReply | ErrorReplyError)[] = [];
for (let i = 0; i < commands.length; i++) {
try {
const rep = await readReply(readable, commands[i].returnUint8Arrays);
ret.push(rep);
} catch (e) {
if (e instanceof ErrorReplyError) {
ret.push(e);
} else {
throw e;
}
}
}
return ret;
}
================================================
FILE: protocol/web_streams/mod.ts
================================================
import { sendCommand, sendCommands, writeCommand } from "./command.ts";
import { readReply } from "./reply.ts";
import type { Command, Protocol as BaseProtocol } from "../shared/protocol.ts";
import type { RedisReply, RedisValue } from "../shared/types.ts";
import type { ErrorReplyError } from "../../errors.ts";
import { BufferedReadableStream } from "../../internal/buffered_readable_stream.ts";
export class Protocol implements BaseProtocol {
#readable: BufferedReadableStream;
#writable: WritableStream<Uint8Array>;
constructor(conn: Deno.Conn) {
this.#readable = new BufferedReadableStream(conn.readable);
this.#writable = conn.writable;
}
sendCommand(
command: string,
args: RedisValue[],
returnsUint8Arrays?: boolean | undefined,
): Promise<RedisReply> {
return sendCommand(
this.#writable,
this.#readable,
command,
args,
returnsUint8Arrays,
);
}
readReply(returnsUint8Arrays?: boolean): Promise<RedisReply> {
return readReply(this.#readable, returnsUint8Arrays);
}
writeCommand(command: Command): Promise<void> {
return writeCommand(this.#writable, command.command, command.args);
}
pipeline(commands: Command[]): Promise<Array<RedisReply | ErrorReplyError>> {
return sendCommands(this.#writable, this.#readable, commands);
}
}
================================================
FILE: protocol/web_streams/reply.ts
================================================
import type * as types from "../shared/types.ts";
import {
ArrayReplyCode,
AttributeReplyCode,
BigNumberReplyCode,
BlobErrorReplyCode,
BooleanReplyCode,
BulkReplyCode,
DoubleReplyCode,
ErrorReplyCode,
IntegerReplyCode,
MapReplyCode,
NullReplyCode,
PushReplyCode,
SetReplyCode,
SimpleStringCode,
VerbatimStringCode,
} from "../shared/reply.ts";
import { ErrorReplyError, NotImplementedError } from "../../errors.ts";
import { decoder } from "../../internal/encoding.ts";
import type { BufferedReadableStream } from "../../internal/buffered_readable_stream.ts";
export async function readReply(
readable: BufferedReadableStream,
returnUint8Arrays?: boolean,
) {
const line = await readable.readLine();
const code = line[0];
switch (code) {
case ErrorReplyCode: {
throw new ErrorReplyError(decoder.decode(line));
}
case IntegerReplyCode: {
return Number.parseInt(decoder.decode(line.subarray(1)));
}
case SimpleStringCode: {
const body = line.slice(1, -2);
return returnUint8Arrays ? body : decoder.decode(body);
}
case BulkReplyCode:
case VerbatimStringCode: {
const size = Number.parseInt(decoder.decode(line.subarray(1)));
if (size < 0) {
// nil bulk reply
return null;
}
const buf = await readable.readN(size + 2);
const body = buf.subarray(0, size); // Strip CR and LF.
return returnUint8Arrays ? body : decoder.decode(body);
}
case BlobErrorReplyCode: {
const size = Number.parseInt(decoder.decode(line.subarray(1)));
const buf = await readable.readN(size + 2);
const body = buf.subarray(0, size); // Strip CR and LF.
throw new ErrorReplyError(decoder.decode(body));
}
case ArrayReplyCode:
case PushReplyCode: {
const size = Number.parseInt(decoder.decode(line.slice(1)));
if (size === -1) {
// `-1` indicates a null array
return null;
}
const array: Array<types.RedisReply> = [];
for (let i = 0; i < size; i++) {
array.push(await readReply(readable, returnUint8Arrays));
}
return array;
}
case MapReplyCode: {
// NOTE: We treat a map type as an array to keep backward compatibility.
const numberOfFieldValuePairs = Number.parseInt(
decoder.decode(line.slice(1)),
);
if (numberOfFieldValuePairs === -1) {
return null;
}
const entries: Array<types.RedisReply> = [];
for (let i = 0; i < (numberOfFieldValuePairs * 2); i++) {
entries.push(await readReply(readable, returnUint8Arrays));
}
return entries;
}
case SetReplyCode: {
// NOTE: We treat a set type as an array to keep backward compatibility.
const size = Number.parseInt(decoder.decode(line.slice(1)));
if (size === -1) {
// `-1` indicates a null set
return null;
}
const set: Array<types.RedisReply> = [];
for (let i = 0; i < size; i++) {
set.push(await readReply(readable, returnUint8Arrays));
}
return set;
}
case BooleanReplyCode: {
const isTrue = line[1] === 116;
return isTrue
? 1 // `#t`
: 0; // `#f`
}
case BigNumberReplyCode:
case DoubleReplyCode: {
const body = line.subarray(1, -2);
return returnUint8Arrays ? body : decoder.decode(body);
}
case NullReplyCode: {
return null;
}
case AttributeReplyCode: {
// NOTE: Currently, we simply drop attributes.
// TODO: Provide a way for users to capture attributes.
const numberOfAttributes = Number.parseInt(
decoder.decode(line.slice(1)),
);
if (numberOfAttributes === -1) {
return readReply(readable, returnUint8Arrays); // Reads the next reply.
}
for (let i = 0; i < numberOfAttributes; i++) {
await readReply(readable, returnUint8Arrays); // Reads a key
await readReply(readable, returnUint8Arrays); // Reads a value
}
return readReply(readable, returnUint8Arrays); // Reads the next reply.
}
default:
throw new NotImplementedError(
`'${String.fromCharCode(code)}' reply is not implemented`,
);
}
}
================================================
FILE: protocol/web_streams/reply_test.ts
================================================
import { assertEquals, assertRejects } from "../../deps/std/assert.ts";
import { readReply } from "./reply.ts";
import { BufferedReadableStream } from "../../internal/buffered_readable_stream.ts";
import { ErrorReplyError } from "../../errors.ts";
import type { RedisReply } from "../shared/types.ts";
Deno.test({
name: "readReply",
permissions: "none",
fn: async (t) => {
await t.step("blob", async () => {
const readable = createReadableByteStream("$12\r\nhello\nworld!\r\n");
const reply = await readReply(new BufferedReadableStream(readable));
assertEquals(reply, "hello\nworld!");
});
await t.step("simple string", async () => {
const readable = createReadableByteStream("+OK\r\n");
const reply = await readReply(new BufferedReadableStream(readable));
assertEquals(reply, "OK");
});
await t.step("integer", async () => {
const readable = createReadableByteStream(":1234\r\n");
const reply = await readReply(new BufferedReadableStream(readable));
assertEquals(reply, 1234);
});
await t.step("array", async () => {
const readable = createReadableByteStream(
"*5\r\n$3\r\nfoo\r\n*2\r\n:456\r\n+OK\r\n_\r\n(123456\r\n:78\r\n",
);
const reply = await readReply(new BufferedReadableStream(readable));
assertEquals(reply, ["foo", [456, "OK"], null, "123456", 78]);
});
await t.step("map", async () => {
const readable = createReadableByteStream(
"%2\r\n+foo\r\n:1\r\n+bar\r\n:2\r\n",
);
const reply = await readReply(new BufferedReadableStream(readable));
assertEquals(reply, ["foo", 1, "bar", 2]);
});
await t.step("set", async () => {
const readable = createReadableByteStream(
"~3\r\n+foo\r\n:5\r\n$3\r\nbar\r\n",
);
const reply = await readReply(new BufferedReadableStream(readable));
assertEquals(reply, ["foo", 5, "bar"]);
});
await t.step("null", async () => {
const readable = createReadableByteStream(
"_\r\n",
);
const reply = await readReply(new BufferedReadableStream(readable));
assertEquals(reply, null);
});
await t.step("blob error", async () => {
const readable = createReadableByteStream(
"!21\r\nSYNTAX invalid syntax\r\n_\r\n",
);
const buf = new BufferedReadableStream(readable);
await assertRejects(
() => readReply(buf),
ErrorReplyError,
"SYNTAX invalid syntax",
);
assertEquals(await readReply(buf), null);
});
await t.step("attribute", async () => {
const cases: Array<[given: string, expected: RedisReply]> = [
[
"|1\r\n+foo\r\n%2\r\n$3\r\nbar\r\n:123\r\n$1\r\na\r\n,1.23\r\n+Hello World\r\n",
"Hello World",
],
[
"*3\r\n:123\r\n|2\r\n+foo\r\n:1\r\n+bar\r\n:45\r\n+str\r\n,1.23\r\n",
[123, "str", "1.23"],
],
];
for (
const [given, expected] of cases
) {
const readable = createReadableByteStream(given);
const buf = new BufferedReadableStream(readable);
const actual = await readReply(buf);
assertEquals(actual, expected);
}
});
},
});
function createReadableByteStream(payload: string): ReadableStream<Uint8Array> {
const encoder = new TextEncoder();
let numRead = 0;
return new ReadableStream({
type: "bytes",
pull(controller) {
if (controller.byobRequest?.view) {
const view = controller.byobRequest.view;
const buf = new Uint8Array(
view.buffer,
view.byteOffset,
view.byteLength,
);
const remaining = payload.length - numRead;
const written = Math.min(buf.byteLength, remaining);
buf.set(encoder.encode(payload.slice(numRead, numRead + written)));
numRead += written;
controller.byobRequest.respond(written);
} else {
controller.enqueue(encoder.encode(payload[numRead]));
numRead++;
}
if (numRead >= payload.length) {
controller.close();
}
},
});
}
================================================
FILE: redis.ts
================================================
import type {
ACLLogMode,
BitfieldOpts,
BitfieldWithOverflowOpts,
ClientCachingMode,
ClientKillOpts,
ClientListOpts,
ClientPauseMode,
ClientTrackingOpts,
ClientUnblockingBehaviour,
ClusterFailoverMode,
ClusterResetMode,
ClusterSetSlotSubcommand,
GeoRadiusOpts,
GeoUnit,
HelloOpts,
HScanOpts,
LInsertLocation,
LPosOpts,
LPosWithCountOpts,
MemoryUsageOpts,
MigrateOpts,
RedisCommands,
RestoreOpts,
ScanOpts,
ScriptDebugMode,
SetOpts,
SetReply,
SetWithModeOpts,
ShutdownMode,
SortOpts,
SortWithDestinationOpts,
SScanOpts,
StralgoAlgorithm,
StralgoOpts,
StralgoTarget,
ZAddOpts,
ZAddReply,
ZInterOpts,
ZInterstoreOpts,
ZRangeByLexOpts,
ZRangeByScoreOpts,
ZRangeOpts,
ZScanOpts,
ZUnionstoreOpts,
} from "./command.ts";
import { createRedisConnection } from "./default_connection.ts";
import type { Connection, SendCommandOptions } from "./connection.ts";
import type { RedisConnectionOptions } from "./connection.ts";
import type { Client } from "./client.ts";
import type { RedisSubscription } from "./subscription.ts";
import { createDefaultClient } from "./default_client.ts";
import type { ConnectionEventMap, ConnectionEventType } from "./events.ts";
import type { TypedEventTarget } from "./internal/typed_event_target.ts";
import type {
Binary,
Bulk,
BulkNil,
BulkString,
ConditionalArray,
Integer,
Raw,
RedisReply,
RedisValue,
SimpleString,
} from "./protocol/shared/types.ts";
import { createRedisPipeline } from "./pipeline.ts";
import type {
StartEndCount,
XAddFieldValues,
XClaimJustXId,
XClaimMessages,
XClaimOpts,
XId,
XIdAdd,
XIdInput,
XIdNeg,
XIdPos,
XKeyId,
XKeyIdGroup,
XKeyIdGroupLike,
XKeyIdLike,
XMaxlen,
XReadGroupOpts,
XReadIdData,
XReadOpts,
XReadStreamRaw,
} from "./stream.ts";
import {
convertMap,
isCondArray,
isNumber,
isString,
parseXGroupDetail,
parseXId,
parseXMessage,
parseXPendingConsumers,
parseXPendingCounts,
parseXReadReply,
rawnum,
rawstr,
xidstr,
} from "./stream.ts";
const binaryCommandOptions = {
returnUint8Arrays: true,
};
/**
* A high-level client for Redis.
*/
export interface Redis
extends RedisCommands, TypedEventTarget<ConnectionEventMap> {
readonly isClosed: boolean;
readonly isConnected: boolean;
/**
* Low level interface for Redis server
*/
sendCommand(
command: string,
args?: RedisValue[],
options?: SendCommandOptions,
): Promise<RedisReply>;
connect(): Promise<void>;
close(): void;
[Symbol.dispose](): void;
}
class RedisImpl implements Redis {
private readonly client: Client;
get isClosed() {
return this.client.connection.isClosed;
}
get isConnected() {
return this.client.connection.isConnected;
}
constructor(client: Client) {
this.client = client;
}
addEventListener<K extends ConnectionEventType>(
type: K,
callback: (event: CustomEvent<ConnectionEventMap[K]>) => void,
options?: boolean | AddEventListenerOptions,
): void {
return this.client.connection.addEventListener(
type,
callback,
options,
);
}
removeEventListener<K extends ConnectionEventType>(
type: K,
callback: (event: CustomEvent<ConnectionEventMap[K]>) => void,
options?: boolean | EventListenerOptions,
): void {
return this.client.connection.removeEventListener(
type,
callback,
options,
);
}
sendCommand(
command: string,
args?: RedisValue[],
options?: SendCommandOptions,
) {
return this.client.sendCommand(command, args, options);
}
connect(): Promise<void> {
return this.client.connection.connect();
}
close(): void {
return this.client.close();
}
[Symbol.dispose](): void {
return this.close();
}
async execReply<T extends Raw = Raw>(
command: string,
...args: RedisValue[]
): Promise<T> {
const reply = await this.client.exec(
command,
...args,
);
return reply as T;
}
async execStatusReply(
command: string,
...args: RedisValue[]
): Promise<SimpleString> {
const reply = await this.client.exec(command, ...args);
return reply as SimpleString;
}
async execIntegerReply(
command: string,
...args: RedisValue[]
): Promise<Integer> {
const reply = await this.client.exec(command, ...args);
return reply as Integer;
}
async execBinaryReply(
command: string,
...args: RedisValue[]
): Promise<Binary | BulkNil> {
const reply = await this.client.sendCommand(
command,
args,
binaryCommandOptions,
);
return reply as Binary | BulkNil;
}
async execBulkReply<T extends Bulk = Bulk>(
command: string,
...args: RedisValue[]
): Promise<T> {
const reply = await this.client.exec(command, ...args);
return reply as T;
}
async execArrayReply<T extends Raw = Raw>(
command: string,
...args: RedisValue[]
): Promise<T[]> {
const reply = await this.client.exec(command, ...args);
return reply as Array<T>;
}
async execIntegerOrNilReply(
command: string,
...args: RedisValue[]
): Promise<Integer | BulkNil> {
const reply = await this.client.exec(command, ...args);
return reply as Integer | BulkNil;
}
async execStatusOrNilReply(
command: string,
...args: RedisValue[]
): Promise<SimpleString | BulkNil> {
const reply = await this.client.exec(command, ...args);
return reply as SimpleString | BulkNil;
}
aclCat(categoryname?: string) {
if (categoryname !== undefined) {
return this.execArrayReply<BulkString>("ACL", "CAT", categoryname);
}
return this.execArrayReply<BulkString>("ACL", "CAT");
}
aclDelUser(...usernames: string[]) {
return this.execIntegerReply("ACL", "DELUSER", ...usernames);
}
aclGenPass(bits?: number) {
if (bits !== undefined) {
return this.execBulkReply<BulkString>("ACL", "GENPASS", bits);
}
return this.execBulkReply<BulkString>("ACL", "GENPASS");
}
aclGetUser(username: string) {
return this.execArrayReply<BulkString | BulkString[]>(
"ACL",
"GETUSER",
username,
);
}
aclHelp() {
return this.execArrayReply<BulkString>("ACL", "HELP");
}
aclList() {
return this.execArrayReply<BulkString>("ACL", "LIST");
}
aclLoad() {
return this.execStatusReply("ACL", "LOAD");
}
aclLog(count: number): Promise<BulkString[]>;
aclLog(mode: ACLLogMode): Promise<SimpleString>;
aclLog(param: number | ACLLogMode) {
if (param === "RESET") {
return this.execStatusReply("ACL", "LOG", "RESET");
}
return this.execArrayReply<BulkString>("ACL", "LOG", param);
}
aclSave() {
return this.execStatusReply("ACL", "SAVE");
}
aclSetUser(username: string, ...rules: string[]) {
return this.execStatusReply("ACL", "SETUSER", username, ...rules);
}
aclUsers() {
return this.execArrayReply<BulkString>("ACL", "USERS");
}
aclWhoami() {
return this.execBulkReply<BulkString>("ACL", "WHOAMI");
}
append(key: string, value: RedisValue) {
return this.execIntegerReply("APPEND", key, value);
}
auth(param1: RedisValue, param2?: RedisValue) {
if (param2 !== undefined) {
return this.execStatusReply("AUTH", param1, param2);
}
return this.execStatusReply("AUTH", param1);
}
bgrewriteaof() {
return this.execStatusReply("BGREWRITEAOF");
}
bgsave() {
return this.execStatusReply("BGSAVE");
}
bitcount(key: string, start?: number, end?: number) {
if (start !== undefined && end !== undefined) {
return this.execIntegerReply("BITCOUNT", key, start, end);
}
return this.execIntegerReply("BITCOUNT", key);
}
bitfield(
key: string,
opts?: BitfieldOpts | BitfieldWithOverflowOpts,
) {
const args: (number | string)[] = [key];
if (opts?.get) {
const { type, offset } = opts.get;
args.push("GET", type, offset);
}
if (opts?.set) {
const { type, offset, value } = opts.set;
args.push("SET", type, offset, value);
}
if (opts?.incrby) {
const { type, offset, increment } = opts.incrby;
args.push("INCRBY", type, offset, increment);
}
if ((opts as BitfieldWithOverflowOpts)?.overflow) {
args.push("OVERFLOW", (opts as BitfieldWithOverflowOpts).overflow);
}
return this.execArrayReply<Integer>("BITFIELD", ...args);
}
bitop(operation: string, destkey: string, ...keys: string[]) {
return this.execIntegerReply("BITOP", operation, destkey, ...keys);
}
bitpos(key: string, bit: number, start?: number, end?: number) {
if (start !== undefined && end !== undefined) {
return this.execIntegerReply("BITPOS", key, bit, start, end);
}
if (start !== undefined) {
return this.execIntegerReply("BITPOS", key, bit, start);
}
return this.execIntegerReply("BITPOS", key, bit);
}
blpop(timeout: number, ...keys: string[]) {
return this.execArrayReply("BLPOP", ...keys, timeout) as Promise<
[BulkString, BulkString] | BulkNil
>;
}
brpop(timeout: number, ...keys: string[]) {
return this.execArrayReply("BRPOP", ...keys, timeout) as Promise<
[BulkString, BulkString] | BulkNil
>;
}
brpoplpush(source: string, destination: string, timeout: number) {
return this.execBulkReply("BRPOPLPUSH", source, destination, timeout);
}
bzpopmin(timeout: number, ...keys: string[]) {
return this.execArrayReply("BZPOPMIN", ...keys, timeout) as Promise<
[BulkString, BulkString, BulkString] | BulkNil
>;
}
bzpopmax(timeout: number, ...keys: string[]) {
return this.execArrayReply("BZPOPMAX", ...keys, timeout) as Promise<
[BulkString, BulkString, BulkString] | BulkNil
>;
}
clientCaching(mode: ClientCachingMode) {
return this.execStatusReply("CLIENT", "CACHING", mode);
}
clientGetName() {
return this.execBulkReply("CLIENT", "GETNAME");
}
clientGetRedir() {
return this.execIntegerReply("CLIENT", "GETREDIR");
}
clientID() {
return this.execIntegerReply("CLIENT", "ID");
}
clientInfo() {
return this.execBulkReply("CLIENT", "INFO");
}
clientKill(opts: ClientKillOpts) {
const args: (string | number)[] = [];
if (opts.addr) {
args.push("ADDR", opts.addr);
}
if (opts.laddr) {
args.push("LADDR", opts.laddr);
}
if (opts.id) {
args.push("ID", opts.id);
}
if (opts.type) {
args.push("TYPE", opts.type);
}
if (opts.user) {
args.push("USER", opts.user);
}
if (opts.skipme) {
args.push("SKIPME", opts.skipme);
}
return this.execIntegerReply("CLIENT", "KILL", ...args);
}
clientList(opts?: ClientListOpts) {
if (opts && opts.type && opts.ids) {
throw new Error("only one of `type` or `ids` can be specified");
}
if (opts && opts.type) {
return this.execBulkReply("CLIENT", "LIST", "TYPE", opts.type);
}
if (opts && opts.ids) {
return this.execBulkReply("CLIENT", "LIST", "ID", ...opts.ids);
}
return this.execBulkReply("CLIENT", "LIST");
}
clientPause(timeout: number, mode?: ClientPauseMode) {
if (mode) {
return this.execStatusReply("CLIENT", "PAUSE", timeout, mode);
}
return this.execStatusReply("CLIENT", "PAUSE", timeout);
}
clientSetName(connectionName: string) {
return this.execStatusReply("CLIENT", "SETNAME", connectionName);
}
clientTracking(opts: ClientTrackingOpts) {
const args: (number | string)[] = [opts.mode];
if (opts.redirect) {
args.push("REDIRECT", opts.redirect);
}
if (opts.prefixes) {
opts.prefixes.forEach((prefix) => {
args.push("PREFIX");
args.push(prefix);
});
}
if (opts.bcast) {
args.push("BCAST");
}
if (opts.optIn) {
args.push("OPTIN");
}
if (opts.optOut) {
args.push("OPTOUT");
}
if (opts.noLoop) {
args.push("NOLOOP");
}
return this.execStatusReply("CLIENT", "TRACKING", ...args);
}
clientTrackingInfo() {
return this.execArrayReply("CLIENT", "TRACKINGINFO");
}
clientUnblock(
id: number,
behaviour?: ClientUnblockingBehaviour,
): Promise<Integer> {
if (behaviour) {
return this.execIntegerReply("CLIENT", "UNBLOCK", id, behaviour);
}
return this.execIntegerReply("CLIENT", "UNBLOCK", id);
}
clientUnpause(): Promise<SimpleString> {
return this.execStatusReply("CLIENT", "UNPAUSE");
}
asking() {
return this.execStatusReply("ASKING");
}
clusterAddSlots(...slots: number[]) {
return this.execStatusReply("CLUSTER", "ADDSLOTS", ...slots);
}
clusterCountFailureReports(nodeId: string) {
return this.execIntegerReply("CLUSTER", "COUNT-FAILURE-REPORTS", nodeId);
}
clusterCountKeysInSlot(slot: number) {
return this.execIntegerReply("CLUSTER", "COUNTKEYSINSLOT", slot);
}
clusterDelSlots(...slots: number[]) {
return this.execStatusReply("CLUSTER", "DELSLOTS", ...slots);
}
clusterFailover(mode?: ClusterFailoverMode) {
if (mode) {
return this.execStatusReply("CLUSTER", "FAILOVER", mode);
}
return this.execStatusReply("CLUSTER", "FAILOVER");
}
clusterFlushSlots() {
return this.execStatusReply("CLUSTER", "FLUSHSLOTS");
}
clusterForget(nodeId: string) {
return this.execStatusReply("CLUSTER", "FORGET", nodeId);
}
clusterGetKeysInSlot(slot: number, count: number) {
return this.execArrayReply<BulkString>(
"CLUSTER",
"GETKEYSINSLOT",
slot,
count,
);
}
clusterInfo() {
return this.execStatusReply("CLUSTER", "INFO");
}
clusterKeySlot(key: string) {
return this.execIntegerReply("CLUSTER", "KEYSLOT", key);
}
clusterMeet(ip: string, port: number) {
return this.execStatusReply("CLUSTER", "MEET", ip, port);
}
clusterMyID() {
return this.execStatusReply("CLUSTER", "MYID");
}
clusterNodes() {
return this.execBulkReply<BulkString>("CLUSTER", "NODES");
}
clusterReplicas(nodeId: string) {
return this.execArrayReply<BulkString>("CLUSTER", "REPLICAS", nodeId);
}
clusterReplicate(nodeId: string) {
return this.execStatusReply("CLUSTER", "REPLICATE", nodeId);
}
clusterReset(mode?: ClusterResetMode) {
if (mode) {
return this.execStatusReply("CLUSTER", "RESET", mode);
}
return this.execStatusReply("CLUSTER", "RESET");
}
clusterSaveConfig() {
return this.execStatusReply("CLUSTER", "SAVECONFIG");
}
clusterSetSlot(
slot: number,
subcommand: ClusterSetSlotSubcommand,
nodeId?: string,
) {
if (nodeId !== undefined) {
return this.execStatusReply(
"CLUSTER",
"SETSLOT",
slot,
subcommand,
nodeId,
);
}
return this.execStatusReply("CLUSTER", "SETSLOT", slot, subcommand);
}
clusterSlaves(nodeId: string) {
return this.execArrayReply<BulkString>("CLUSTER", "SLAVES", nodeId);
}
clusterSlots() {
return this.execArrayReply("CLUSTER", "SLOTS");
}
command() {
return this.execArrayReply("COMMAND") as Promise<
[BulkString, Integer, BulkString[], Integer, Integer, Integer][]
>;
}
commandCount() {
return this.execIntegerReply("COMMAND", "COUNT");
}
commandGetKeys() {
return this.execArrayReply<BulkString>("COMMAND", "GETKEYS");
}
commandInfo(...commandNames: string[]) {
return this.execArrayReply("COMMAND", "INFO", ...commandNames) as Promise<
(
| [BulkString, Integer, BulkString[], Integer, Integer, Integer]
| BulkNil
)[]
>;
}
configGet(parameter: string) {
return this.execArrayReply<BulkString>("CONFIG", "GET", parameter);
}
configResetStat() {
return this.execStatusReply("CONFIG", "RESETSTAT");
}
configRewrite() {
return this.execStatusReply("CONFIG", "REWRITE");
}
configSet(parameter: string, value: string | number) {
return this.execStatusReply("CONFIG", "SET", parameter, value);
}
dbsize() {
return this.execIntegerReply("DBSIZE");
}
debugObject(key: string) {
return this.execStatusReply("DEBUG", "OBJECT", key);
}
debugSegfault() {
return this.execStatusReply("DEBUG", "SEGFAULT");
}
decr(key: string) {
return this.execIntegerReply("DECR", key);
}
decrby(key: string, decrement: number) {
return this.execIntegerReply("DECRBY", key, decrement);
}
del(...keys: string[]) {
return this.execIntegerReply("DEL", ...keys);
}
discard() {
return this.execStatusReply("DISCARD");
}
dump(key: string) {
return this.execBinaryReply("DUMP", key);
}
echo(message: RedisValue) {
return this.execBulkReply<BulkString>("ECHO", message);
}
eval(script: string, keys: string[], args: string[]) {
return this.execReply(
"EVAL",
script,
keys.length,
...keys,
...args,
);
}
evalsha(sha1: string, keys: string[], args: string[]) {
return this.execReply(
"EVALSHA",
sha1,
keys.length,
...keys,
...args,
);
}
exec() {
return this.execArrayReply("EXEC");
}
exists(...keys: string[]) {
return this.execIntegerReply("EXISTS", ...keys);
}
expire(key: string, seconds: number) {
return this.execIntegerReply("EXPIRE", key, seconds);
}
expireat(key: string, timestamp: string) {
return this.execIntegerReply("EXPIREAT", key, timestamp);
}
flushall(async?: boolean) {
if (async) {
return this.execStatusReply("FLUSHALL", "ASYNC");
}
return this.execStatusReply("FLUSHALL");
}
flushdb(async?: boolean) {
if (async) {
return this.execStatusReply("FLUSHDB", "ASYNC");
}
return this.execStatusReply("FLUSHDB");
}
// deno-lint-ignore no-explicit-any -- TODO: improve the signature not to use `any`
geoadd(key: string, ...params: any[]) {
const args: (string | number)[] = [key];
if (Array.isArray(params[0])) {
args.push(...params.flatMap((e) => e));
} else if (typeof params[0] === "object") {
for (const [member, lnglat] of Object.entries(params[0])) {
args.push(...(lnglat as [number, number]), member);
}
} else {
args.push(...params);
}
return this.execIntegerReply("GEOADD", ...args);
}
geohash(key: string, ...members: string[]) {
return this.execArrayReply<Bulk>("GEOHASH", key, ...members);
}
geopos(key: string, ...members: string[]) {
return this.execArrayReply("GEOPOS", key, ...members) as Promise<
([BulkString, BulkString] | BulkNil | [])[]
>;
}
geodist(
key: string,
member1: string,
member2: string,
unit?: GeoUnit,
) {
if (unit) {
return this.execBulkReply("GEODIST", key, member1, member2, unit);
}
return this.execBulkReply("GEODIST", key, member1, member2);
}
georadius(
key: string,
longitude: number,
latitude: number,
radius: number,
unit: "m" | "km" | "ft" | "mi",
opts?: GeoRadiusOpts,
) {
const args = this.pushGeoRadiusOpts(
[key, longitude, latitude, radius, unit],
opts,
);
return this.execArrayReply("GEORADIUS", ...args);
}
georadiusbymember(
key: string,
member: string,
radius: number,
unit: GeoUnit,
opts?: GeoRadiusOpts,
) {
const args = this.pushGeoRadiusOpts([key, member, radius, unit], opts);
return this.execArrayReply("GEORADIUSBYMEMBER", ...args);
}
private pushGeoRadiusOpts(
args: (string | number)[],
opts?: GeoRadiusOpts,
) {
if (opts?.withCoord) {
args.push("WITHCOORD");
}
if (opts?.withDist) {
args.push("WITHDIST");
}
if (opts?.withHash) {
args.push("WITHHASH");
}
if (opts?.count !== undefined) {
args.push(opts.count);
}
if (opts?.sort) {
args.push(opts.sort);
}
if (opts?.store !== undefined) {
args.push(opts.store);
}
if (opts?.storeDist !== undefined) {
args.push(opts.storeDist);
}
return args;
}
get(key: string) {
return this.execBulkReply("GET", key);
}
getbit(key: string, offset: number) {
return this.execIntegerReply("GETBIT", key, offset);
}
getrange(key: string, start: number, end: number) {
return this.execBulkReply<BulkString>("GETRANGE", key, start, end);
}
getset(key: string, value: RedisValue) {
return this.execBulkReply("GETSET", key, value);
}
hdel(key: string, ...fields: string[]) {
return this.execIntegerReply("HDEL", key, ...fields);
}
hexists(key: string, field: string) {
return this.execIntegerReply("HEXISTS", key, field);
}
hget(key: string, field: string) {
return this.execBulkReply("HGET", key, field);
}
hgetall(key: string) {
return this.execArrayReply<BulkString>("HGETALL", key);
}
hincrby(key: string, field: string, increment: number) {
return this.execIntegerReply("HINCRBY", key, field, increment);
}
hincrbyfloat(key: string, field: string, increment: number) {
return this.execBulkReply<BulkString>(
"HINCRBYFLOAT",
key,
field,
increment,
);
}
hkeys(key: string) {
return this.execArrayReply<BulkString>("HKEYS", key);
}
hlen(key: string) {
return this.execIntegerReply("HLEN", key);
}
hmget(key: string, ...fields: string[]) {
return this.execArrayReply<Bulk>("HMGET", key, ...fields);
}
// deno-lint-ignore no-explicit-any -- TODO: improve the signature not to use `any`
hmset(key: string, ...params: any[]) {
const args = [key] as RedisValue[];
if (Array.isArray(params[0])) {
args.push(...params.flatMap((e) => e));
} else if (typeof params[0] === "object") {
for (const [field, value] of Object.entries(params[0])) {
args.push(field, value as RedisValue);
}
} else {
args.push(...params);
}
return this.execStatusReply("HMSET", ...args);
}
// deno-lint-ignore no-explicit-any -- TODO: improve the signature not to use `any`
hset(key: string, ...params: any[]) {
const args = [key] as RedisValue[];
if (Array.isArray(params[0])) {
args.push(...params.flatMap((e) => e));
} else if (typeof params[0] === "object") {
for (const [field, value] of Object.entries(params[0])) {
args.push(field, value as RedisValue);
}
} else {
args.push(...params);
}
return this.execIntegerReply("HSET", ...args);
}
hsetnx(key: string, field: string, value: RedisValue) {
return this.execIntegerReply("HSETNX", key, field, value);
}
hstrlen(key: string, field: string) {
return this.execIntegerReply("HSTRLEN", key, field);
}
hvals(key: string) {
return this.execArrayReply<BulkString>("HVALS", key);
}
incr(key: string) {
return this.execIntegerReply("INCR", key);
}
incrby(key: string, increment: number) {
return this.execIntegerReply("INCRBY", key, increment);
}
incrbyfloat(key: string, increment: number) {
return this.execBulkReply<BulkString>("INCRBYFLOAT", key, increment);
}
info(section?: string) {
if (section !== undefined) {
return this.execStatusReply("INFO", section);
}
return this.execStatusReply("INFO");
}
keys(pattern: string) {
return this.execArrayReply<BulkString>("KEYS", pattern);
}
lastsave() {
return this.execIntegerReply("LASTSAVE");
}
lindex(key: string, index: number) {
return this.execBulkReply("LINDEX", key, index);
}
linsert(key: string, loc: LInsertLocation, pivot: string, value: RedisValue) {
return this.execIntegerReply("LINSERT", key, loc, pivot, value);
}
llen(key: string) {
return this.execIntegerReply("LLEN", key);
}
lpop(key: string): Promise<Bulk>;
lpop(key: string, count: number): Promise<Array<BulkString>>;
lpop(key: string, count?: number): Promise<Bulk | Array<BulkString>> {
if (count == null) {
return this.execBulkReply("LPOP", key);
} else {
return this.execArrayReply("LPOP", key, count);
}
}
lpos(
key: string,
element: RedisValue,
opts?: LPosOpts,
): Promise<Integer | BulkNil>;
lpos(
key: string,
element: RedisValue,
opts: LPosWithCountOpts,
): Promise<Integer[]>;
lpos(
key: string,
element: RedisValue,
opts?: LPosOpts | LPosWithCountOpts,
): Promise<Integer | BulkNil | Integer[]> {
const args = [element];
if (opts?.rank != null) {
args.push("RANK", String(opts.rank));
}
if (opts?.count != null) {
args.push("COUNT", String(opts.count));
}
if (opts?.maxlen != null) {
args.push("MAXLEN", String(opts.maxlen));
}
return opts?.count == null
? this.execIntegerReply("LPOS", key, ...args)
: this.execArrayReply<Integer>("LPOS", key, ...args);
}
lpush(key: string, ...elements: RedisValue[]) {
return this.execIntegerReply("LPUSH", key, ...elements);
}
lpushx(key: string, ...elements: RedisValue[]) {
return this.execIntegerReply("LPUSHX", key, ...elements);
}
lrange(key: string, start: number, stop: number) {
return this.execArrayReply<BulkString>("LRANGE", key, start, stop);
}
lrem(key: string, count: number, element: string | number) {
return this.execIntegerReply("LREM", key, count, element);
}
lset(key: string, index: number, element: string | number) {
return this.execStatusReply("LSET", key, index, element);
}
ltrim(key: string, start: number, stop: number) {
return this.execStatusReply("LTRIM", key, start, stop);
}
memoryDoctor() {
return this.execBulkReply<BulkString>("MEMORY", "DOCTOR");
}
memoryHelp() {
return this.execArrayReply<BulkString>("MEMORY", "HELP");
}
memoryMallocStats() {
return this.execBulkReply<BulkString>("MEMORY", "MALLOC", "STATS");
}
memoryPurge() {
return this.execStatusReply("MEMORY", "PURGE");
}
memoryStats() {
return this.execArrayReply("MEMORY", "STATS");
}
memoryUsage(key: string, opts?: MemoryUsageOpts) {
const args: (number | string)[] = [key];
if (opts?.samples !== undefined) {
args.push("SAMPLES", opts.samples);
}
return this.execIntegerReply("MEMORY", "USAGE", ...args);
}
mget(...keys: string[]) {
return this.execArrayReply<Bulk>("MGET", ...keys);
}
migrate(
host: string,
port: number,
key: string,
destinationDB: string,
timeout: number,
opts?: MigrateOpts,
) {
const args = [host, port, key, destinationDB, timeout];
if (opts?.copy) {
args.push("COPY");
}
if (opts?.replace) {
args.push("REPLACE");
}
if (opts?.auth !== undefined) {
args.push("AUTH", opts.auth);
}
if (opts?.keys) {
args.push("KEYS", ...opts.keys);
}
return this.execStatusReply("MIGRATE", ...args);
}
moduleList() {
return this.execArrayReply<BulkString>("MODULE", "LIST");
}
moduleLoad(path: string, ...args: string[]) {
return this.execStatusReply("MODULE", "LOAD", path, ...args);
}
moduleUnload(name: string) {
return this.execStatusReply("MODULE", "UNLOAD", name);
}
monitor() {
throw new Error("not supported yet");
}
move(key: string, db: string) {
return this.execIntegerReply("MOVE", key, db);
}
// deno-lint-ignore no-explicit-any -- TODO: improve the signature not to use `any`
mset(...params: any[]) {
const args: RedisValue[] = [];
if (Array.isArray(params[0])) {
args.push(...params.flatMap((e) => e));
} else if (typeof params[0] === "object") {
for (const [key, value] of Object.entries(params[0])) {
args.push(key, value as RedisValue);
}
} else {
args.push(...params);
}
return this.execStatusReply("MSET", ...args);
}
// deno-lint-ignore no-explicit-any -- TODO: improve the signature not to use `any`
msetnx(...params: any[]) {
const args: RedisValue[] = [];
if (Array.isArray(params[0])) {
args.push(...params.flatMap((e) => e));
} else if (typeof params[0] === "object") {
for (const [key, value] of Object.entries(params[0])) {
args.push(key, value as RedisValue);
}
} else {
args.push(...params);
}
return this.execIntegerReply("MSETNX", ...args);
}
multi() {
return this.execStatusReply("MULTI");
}
objectEncoding(key: string) {
return this.execBulkReply("OBJECT", "ENCODING", key);
}
objectFreq(key: string) {
return this.execIntegerOrNilReply("OBJECT", "FREQ", key);
}
objectHelp() {
return this.execArrayReply<BulkString>("OBJECT", "HELP");
}
objectIdletime(key: string) {
return this.execIntegerOrNilReply("OBJECT", "IDLETIME", key);
}
objectRefCount(key: string) {
return this.execIntegerOrNilReply("OBJECT", "REFCOUNT", key);
}
persist(key: string) {
return this.execIntegerReply("PERSIST", key);
}
pexpire(key: string, milliseconds: number) {
return this.execIntegerReply("PEXPIRE", key, milliseconds);
}
pexpireat(key: string, millisecondsTimestamp: number) {
return this.execIntegerReply("PEXPIREAT", key, millisecondsTimestamp);
}
pfadd(key: string, ...elements: string[]) {
return this.execIntegerReply("PFADD", key, ...elements);
}
pfcount(...keys: string[]) {
return this.execIntegerReply("PFCOUNT", ...keys);
}
pfmerge(destkey: string, ...sourcekeys: string[]) {
return this.execStatusReply("PFMERGE", destkey, ...sourcekeys);
}
ping(message?: RedisValue) {
if (message) {
return this.execBulkReply<BulkString>("PING", message);
}
return this.execStatusReply("PING");
}
psetex(key: string, milliseconds: number, value: RedisValue) {
return this.execStatusReply("PSETEX", key, milliseconds, value);
}
publish(channel: string, message: string) {
return this.execIntegerReply("PUBLISH", channel, message);
}
// deno-lint-ignore no-explicit-any -- This is a private property.
#subscription?: RedisSubscription<any>;
async subscribe<TMessage extends string | string[] = string>(
...channels: string[]
) {
if (this.#subscription) {
await this.#subscription.subscribe(...channels);
return this.#subscription;
}
const subscription = await this.client.subscribe<TMessage>(
"SUBSCRIBE",
...channels,
);
this.#subscription = subscription;
return subscription;
}
async psubscribe<TMessage extends string | string[] = string>(
...patterns: string[]
) {
if (this.#subscription) {
await this.#subscription.psubscribe(...patterns);
return this.#subscription;
}
const subscription = await this.client.subscribe<TMessage>(
"PSUBSCRIBE",
...patterns,
);
this.#subscription = subscription;
return subscription;
}
pubsubChannels(pattern?: string) {
if (pattern !== undefined) {
return this.execArrayReply<BulkString>("PUBSUB", "CHANNELS", pattern);
}
return this.execArrayReply<BulkString>("PUBSUB", "CHANNELS");
}
pubsubNumpat() {
return this.execIntegerReply("PUBSUB", "NUMPAT");
}
pubsubNumsub(...channels: string[]) {
return this.execArrayReply<BulkString | Integer>(
"PUBSUB",
"NUMSUB",
...channels,
);
}
pttl(key: string) {
return this.execIntegerReply("PTTL", key);
}
quit() {
return this.execStatusReply("QUIT").finally(() => this.close());
}
randomkey() {
return this.execBulkReply("RANDOMKEY");
}
readonly() {
return this.execStatusReply("READONLY");
}
readwrite() {
return this.execStatusReply("READWRITE");
}
rename(key: string, newkey: string) {
return this.execStatusReply("RENAME", key, newkey);
}
renamenx(key: string, newkey: string) {
return this.execIntegerReply("RENAMENX", key, newkey);
}
restore(
key: string,
ttl: number,
serializedValue: Binary,
opts?: RestoreOpts,
) {
const args = [key, ttl, serializedValue];
if (opts?.replace) {
args.push("REPLACE");
}
if (opts?.absttl) {
args.push("ABSTTL");
}
if (opts?.idletime !== undefined) {
args.push("IDLETIME", opts.idletime);
}
if (opts?.freq !== undefined) {
args.push("FREQ", opts.freq);
}
return this.execStatusReply("RESTORE", ...args);
}
role() {
return this.execArrayReply("ROLE"
gitextract_mcsn30us/
├── .denov
├── .editorconfig
├── .github/
│ ├── CODEOWNERS
│ ├── FUNDING.yml
│ ├── dependabot.yml
│ ├── pinact.yaml
│ └── workflows/
│ ├── build.yml
│ └── publish.yml
├── .gitignore
├── .octocov.yml
├── LICENSE
├── README.md
├── backoff.ts
├── benchmark/
│ ├── .npmrc
│ ├── benchmark.js
│ ├── deno-redis.ts
│ ├── ioredis.js
│ └── package.json
├── client.ts
├── command.ts
├── connection.ts
├── default_client.ts
├── default_connection.ts
├── default_subscription.ts
├── deno.json
├── deps/
│ ├── cluster-key-slot.js
│ └── std/
│ ├── assert.ts
│ ├── async.ts
│ ├── bytes.ts
│ ├── collections.ts
│ ├── io.ts
│ ├── random.ts
│ └── testing.ts
├── errors.ts
├── events.ts
├── executor.ts
├── experimental/
│ ├── README.md
│ ├── cluster/
│ │ ├── README.md
│ │ └── mod.ts
│ ├── pool/
│ │ └── mod.ts
│ └── web_streams_connection/
│ └── mod.ts
├── import_map.dev.json
├── import_map.test.json
├── internal/
│ ├── buffered_readable_stream.ts
│ ├── buffered_readable_stream_test.ts
│ ├── concate_bytes.ts
│ ├── concate_bytes_test.ts
│ ├── delegate.ts
│ ├── delegate_test.ts
│ ├── encoding.ts
│ ├── on.ts
│ ├── on_test.ts
│ ├── symbols.ts
│ └── typed_event_target.ts
├── mod.ts
├── pipeline.ts
├── pool/
│ ├── client.ts
│ ├── default_pool.ts
│ ├── default_pool_test.ts
│ ├── mod.ts
│ └── pool.ts
├── protocol/
│ ├── deno_streams/
│ │ ├── command.ts
│ │ ├── mod.ts
│ │ ├── reply.ts
│ │ └── reply_test.ts
│ ├── shared/
│ │ ├── command.ts
│ │ ├── command_test.ts
│ │ ├── protocol.ts
│ │ ├── reply.ts
│ │ └── types.ts
│ └── web_streams/
│ ├── command.ts
│ ├── mod.ts
│ ├── reply.ts
│ └── reply_test.ts
├── redis.ts
├── stream.ts
├── subscription.ts
├── tests/
│ ├── backoff_test.ts
│ ├── client_test.ts
│ ├── cluster/
│ │ ├── test.ts
│ │ └── test_util.ts
│ ├── cluster_test.ts
│ ├── commands/
│ │ ├── acl.ts
│ │ ├── connection.ts
│ │ ├── general.ts
│ │ ├── geo.ts
│ │ ├── hash.ts
│ │ ├── hyper_loglog.ts
│ │ ├── key.ts
│ │ ├── latency.ts
│ │ ├── list.ts
│ │ ├── pipeline.ts
│ │ ├── pubsub.ts
│ │ ├── resp3.ts
│ │ ├── script.ts
│ │ ├── set.ts
│ │ ├── sorted_set.ts
│ │ ├── stream.ts
│ │ └── string.ts
│ ├── commands_test.ts
│ ├── pool_test.ts
│ ├── reconnect_test.ts
│ ├── server/
│ │ └── redis.conf
│ ├── test_util.ts
│ └── util_test.ts
└── tools/
└── format-benchmark-results.js
SYMBOL INDEX (662 symbols across 60 files)
FILE: backoff.ts
type Backoff (line 1) | interface Backoff {
type ExponentialBackoffOptions (line 8) | interface ExponentialBackoffOptions {
function exponentialBackoff (line 25) | function exponentialBackoff({
FILE: benchmark/benchmark.js
function run (line 4) | function run({
FILE: client.ts
type Client (line 13) | interface Client {
FILE: command.ts
type ACLLogMode (line 42) | type ACLLogMode = "RESET";
type BitopOperation (line 43) | type BitopOperation = "AND" | "OR" | "XOR" | "NOT";
type BitfieldOpts (line 45) | interface BitfieldOpts {
type BitfieldWithOverflowOpts (line 51) | interface BitfieldWithOverflowOpts extends BitfieldOpts {
type ClientCachingMode (line 55) | type ClientCachingMode = "YES" | "NO";
type ClientKillOpts (line 57) | interface ClientKillOpts {
type ClientListOpts (line 66) | interface ClientListOpts {
type ClientPauseMode (line 71) | type ClientPauseMode = "WRITE" | "ALL";
type ClientTrackingOpts (line 73) | interface ClientTrackingOpts {
type ClientType (line 83) | type ClientType = "NORMAL" | "MASTER" | "REPLICA" | "PUBSUB";
type ClientUnblockingBehaviour (line 84) | type ClientUnblockingBehaviour = "TIMEOUT" | "ERROR";
type ClusterFailoverMode (line 86) | type ClusterFailoverMode = "FORCE" | "TAKEOVER";
type ClusterResetMode (line 87) | type ClusterResetMode = "HARD" | "SOFT";
type ClusterSetSlotSubcommand (line 88) | type ClusterSetSlotSubcommand =
type MigrateOpts (line 94) | interface MigrateOpts {
type RestoreOpts (line 101) | interface RestoreOpts {
type HelloOpts (line 108) | interface HelloOpts {
type StralgoOpts (line 117) | interface StralgoOpts {
type StralgoAlgorithm (line 124) | type StralgoAlgorithm = "LCS";
type StralgoTarget (line 125) | type StralgoTarget = "KEYS" | "STRINGS";
type SetOpts (line 127) | interface SetOpts {
type SetReply (line 155) | type SetReply<T extends SetOpts | SetWithModeOpts> = T extends
type SetWithModeOpts (line 165) | interface SetWithModeOpts extends SetOpts {
type GeoRadiusOpts (line 172) | interface GeoRadiusOpts {
type GeoUnit (line 182) | type GeoUnit = "m" | "km" | "ft" | "mi";
type BaseScanOpts (line 184) | interface BaseScanOpts {
type ScanOpts (line 189) | interface ScanOpts extends BaseScanOpts {
type HScanOpts (line 193) | type HScanOpts = BaseScanOpts;
type SScanOpts (line 194) | type SScanOpts = BaseScanOpts;
type ZScanOpts (line 195) | type ZScanOpts = BaseScanOpts;
type ZAddOpts (line 197) | interface ZAddOpts {
type ZAddReply (line 207) | type ZAddReply<T extends ZAddOpts> =
type ZStoreOpts (line 216) | interface ZStoreOpts {
type ZInterstoreOpts (line 220) | type ZInterstoreOpts = ZStoreOpts;
type ZUnionstoreOpts (line 221) | type ZUnionstoreOpts = ZStoreOpts;
type ZRangeOpts (line 223) | interface ZRangeOpts {
type ZInterOpts (line 227) | type ZInterOpts = {
type ZRangeByLexOpts (line 231) | interface ZRangeByLexOpts {
type ZRangeByScoreOpts (line 235) | interface ZRangeByScoreOpts {
type BaseLPosOpts (line 240) | interface BaseLPosOpts {
type LPosOpts (line 245) | interface LPosOpts extends BaseLPosOpts {
type LPosWithCountOpts (line 249) | interface LPosWithCountOpts extends BaseLPosOpts {
type LInsertLocation (line 253) | type LInsertLocation = "BEFORE" | "AFTER";
type MemoryUsageOpts (line 255) | interface MemoryUsageOpts {
type RoleReply (line 259) | type RoleReply =
type ScriptDebugMode (line 264) | type ScriptDebugMode = "YES" | "SYNC" | "NO";
type SortOpts (line 266) | interface SortOpts {
type SortWithDestinationOpts (line 274) | interface SortWithDestinationOpts extends SortOpts {
type ShutdownMode (line 278) | type ShutdownMode = "NOSAVE" | "SAVE";
type RedisCommands (line 280) | interface RedisCommands {
FILE: connection.ts
type SendCommandOptions (line 20) | interface SendCommandOptions {
type Connection (line 29) | interface Connection extends TypedEventTarget<ConnectionEventMap> {
type RedisConnectionOptions (line 65) | interface RedisConnectionOptions {
FILE: default_client.ts
function createDefaultClient (line 12) | function createDefaultClient(connection: Connection): Client {
class DefaultClient (line 16) | class DefaultClient implements Client {
method constructor (line 17) | constructor(readonly connection: Connection) {}
method exec (line 19) | exec(
method sendCommand (line 26) | sendCommand(
method subscribe (line 34) | async subscribe<
method close (line 54) | close(): void {
FILE: default_connection.ts
function createRedisConnection (line 33) | function createRedisConnection(
type PendingCommand (line 41) | interface PendingCommand {
class RedisConnection (line 47) | class RedisConnection
method isClosed (line 64) | get isClosed(): boolean {
method isConnected (line 68) | get isConnected(): boolean {
method isRetriable (line 72) | get isRetriable(): boolean {
method constructor (line 76) | constructor(
method authenticate (line 92) | private async authenticate(
method selectDb (line 115) | private async selectDb(
method enqueueCommand (line 122) | private enqueueCommand(
method sendCommand (line 132) | sendCommand(
method #sendCommandImmediately (line 152) | #sendCommandImmediately(
method addEventListener (line 170) | addEventListener<K extends keyof ConnectionEventMap>(
method removeEventListener (line 182) | removeEventListener<K extends keyof ConnectionEventMap>(
method [kUnstableReadReply] (line 194) | [kUnstableReadReply](returnsUint8Arrays?: boolean): Promise<RedisReply> {
method [kUnstablePipeline] (line 198) | [kUnstablePipeline](commands: Array<Command>): Promise<RedisReply[]> {
method [kUnstableWriteCommand] (line 207) | [kUnstableWriteCommand](command: Command): Promise<void> {
method [kUnstableStartReadLoop] (line 211) | async *[kUnstableStartReadLoop](
method connect (line 244) | connect(): Promise<void> {
method #connect (line 263) | async #connect(retryCount: number) {
method close (line 330) | close() {
method #close (line 338) | #close(canReconnect = false) {
method reconnect (line 361) | async reconnect(): Promise<void> {
method processCommandQueue (line 374) | private async processCommandQueue() {
method isManuallyClosedByUser (line 417) | private isManuallyClosedByUser(): boolean {
method #enableHealthCheckIfNeeded (line 421) | #enableHealthCheckIfNeeded() {
method [Symbol.dispose] (line 334) | [Symbol.dispose](): void {
class AuthenticationError (line 447) | class AuthenticationError extends Error {}
function parsePortLike (line 449) | function parsePortLike(port: string | number | undefined): number {
FILE: default_subscription.ts
class DefaultRedisSubscription (line 15) | class DefaultRedisSubscription<
method isConnected (line 18) | get isConnected(): boolean {
method isClosed (line 22) | get isClosed(): boolean {
method constructor (line 29) | constructor(private readonly connection: Connection) {}
method psubscribe (line 31) | async psubscribe(...patterns: string[]) {
method punsubscribe (line 38) | async punsubscribe(...patterns: string[]) {
method subscribe (line 45) | async subscribe(...channels: string[]) {
method unsubscribe (line 52) | async unsubscribe(...channels: string[]) {
method receive (line 59) | receive(): AsyncIterableIterator<RedisPubSubMessage<TMessage>> {
method receiveBuffers (line 63) | receiveBuffers(): AsyncIterableIterator<RedisPubSubMessage<Binary>> {
method #receive (line 67) | async *#receive<
method close (line 127) | close() {
method #writeCommand (line 131) | async #writeCommand(command: string, args: Array<string>): Promise<voi...
FILE: errors.ts
class EOFError (line 1) | class EOFError extends Error {}
class ConnectionClosedError (line 3) | class ConnectionClosedError extends Error {}
class SubscriptionClosedError (line 5) | class SubscriptionClosedError extends Error {}
class ErrorReplyError (line 7) | class ErrorReplyError extends Error {}
class NotImplementedError (line 8) | class NotImplementedError extends Error {
method constructor (line 9) | constructor(message?: string) {
class InvalidStateError (line 14) | class InvalidStateError extends Error {
method constructor (line 15) | constructor(message?: string) {
function isRetriableError (line 21) | function isRetriableError(error: unknown): boolean {
FILE: events.ts
type ConnectionEvent (line 1) | type ConnectionEvent = Record<string, unknown>;
type ConnectionErrorEventDetails (line 3) | type ConnectionErrorEventDetails = {
type ConnectionReconnectingEventDetails (line 7) | type ConnectionReconnectingEventDetails = {
type ConnectionEventMap (line 11) | type ConnectionEventMap = {
type ConnectionEventType (line 20) | type ConnectionEventType =
FILE: executor.ts
type CommandExecutor (line 4) | type CommandExecutor = Client;
FILE: experimental/cluster/mod.ts
type ClusterConnectOptions (line 47) | interface ClusterConnectOptions {
type NodeOptions (line 53) | interface NodeOptions {
type SlotMap (line 58) | interface SlotMap {
class ClusterNode (line 62) | class ClusterNode {
method constructor (line 65) | constructor(readonly hostname: string, readonly port: number) {
method parseIPAndPort (line 69) | static parseIPAndPort(ipAndPort: string): ClusterNode {
class ClusterError (line 81) | class ClusterError extends Error {}
class ClusterClient (line 83) | class ClusterClient implements Client {
method constructor (line 91) | constructor(opts: ClusterConnectOptions) {
method connection (line 99) | get connection(): Connection {
method exec (line 103) | exec(command: string, ...args: RedisValue[]): Promise<RedisReply> {
method sendCommand (line 107) | async sendCommand(
method subscribe (line 194) | subscribe<TMessage extends PubSubMessageType = DefaultPubSubMessageType>(
method close (line 201) | close(): void {
method initializeSlotsCache (line 213) | async initializeSlotsCache(): Promise<void> {
method #populateStartupNodes (line 245) | #populateStartupNodes(nodes: ClusterNode[]) {
method #getRandomConnection (line 256) | async #getRandomConnection(): Promise<Redis> {
method #getConnectionBySlot (line 287) | #getConnectionBySlot(slot: number): Promise<Redis> {
method #getConnectionByNode (line 296) | async #getConnectionByNode(node: ClusterNode): Promise<Redis> {
method #closeExistingConnection (line 312) | async #closeExistingConnection() {
method #getRedisLink (line 327) | #getRedisLink(node: ClusterNode): Promise<Redis> {
function getKeyFromCommand (line 333) | function getKeyFromCommand(command: string, args: RedisValue[]): string ...
function connectToCluster (line 353) | async function connectToCluster(opts: ClusterConnectOptions): Promise<Re...
FILE: experimental/web_streams_connection/mod.ts
function createProtocol (line 10) | function createProtocol(conn: Deno.Conn) {
function connect (line 14) | function connect(options: RedisConnectOptions): Promise<Redis> {
FILE: internal/buffered_readable_stream.ts
class BufferedReadableStream (line 9) | class BufferedReadableStream {
method constructor (line 12) | constructor(readable: ReadableStream<Uint8Array>) {
method readLine (line 17) | async readLine(): Promise<Uint8Array> {
method readN (line 29) | async readN(n: number): Promise<Uint8Array> {
method #consume (line 63) | #consume(n: number): Uint8Array {
method #fill (line 69) | async #fill() {
FILE: internal/buffered_readable_stream_test.ts
method start (line 62) | start(controller) {
function createReadableStreamFromString (line 88) | function createReadableStreamFromString(s: string): ReadableStream<Uint8...
FILE: internal/concate_bytes.ts
function concateBytes (line 1) | function concateBytes(a: Uint8Array, b: Uint8Array) {
FILE: internal/delegate.ts
type Delegate (line 1) | type Delegate<TObject, TMethods extends keyof TObject> =
function delegate (line 9) | function delegate<
FILE: internal/delegate_test.ts
class Connection (line 12) | class Connection {
method connect (line 14) | connect(): void {
method close (line 17) | close(): void {
method isConnected (line 20) | isConnected(): boolean {
method isClosed (line 23) | isClosed(): boolean {
FILE: internal/on.ts
type Options (line 1) | interface Options {
function on (line 7) | function on(
FILE: internal/typed_event_target.ts
type TypedEventTarget (line 1) | interface TypedEventTarget<TEventMap extends Record<string, unknown>>
function createTypedEventTarget (line 24) | function createTypedEventTarget<
function dispatchEvent (line 30) | function dispatchEvent<
FILE: pipeline.ts
type RedisPipeline (line 20) | interface RedisPipeline extends Redis {
function createRedisPipeline (line 24) | function createRedisPipeline(
class PipelineClient (line 36) | class PipelineClient implements Client {
method constructor (line 52) | constructor(
method exec (line 58) | exec(
method sendCommand (line 65) | sendCommand(
method close (line 78) | close(): void {
method subscribe (line 82) | subscribe<TMessage extends PubSubMessageType = DefaultPubSubMessageType>(
method flush (line 89) | flush(): Promise<RawOrError[]> {
method dequeue (line 103) | private dequeue(): void {
FILE: pool/client.ts
function createPoolClient (line 20) | function createPoolClient(pool: Pool<Connection>): Client {
class PoolClient (line 24) | class PoolClient implements Client {
method constructor (line 26) | constructor(pool: Pool<Connection>) {
method connection (line 30) | get connection(): Connection {
method exec (line 34) | async exec(
method sendCommand (line 47) | async sendCommand(
method subscribe (line 61) | async subscribe<
method close (line 83) | close(): void {
function createPoolConnection (line 88) | function createPoolConnection(
FILE: pool/default_pool.ts
class AlreadyRemovedFromPoolError (line 3) | class AlreadyRemovedFromPoolError extends Error {
method constructor (line 4) | constructor() {
class DefaultPool (line 10) | class DefaultPool<T extends Disposable> implements Pool<T> {
method constructor (line 17) | constructor(
method acquire (line 29) | async acquire(signal?: AbortSignal): Promise<T> {
method #has (line 62) | #has(conn: T): boolean {
method release (line 66) | release(conn: T): void {
method close (line 77) | close() {
type PoolOptions (line 94) | interface PoolOptions<T extends Disposable> {
function createDefaultPool (line 99) | function createDefaultPool<T extends Disposable>(
FILE: pool/default_pool_test.ts
class FakeConnection (line 4) | class FakeConnection implements Disposable {
method isClosed (line 6) | isClosed() {
method [Symbol.dispose] (line 9) | [Symbol.dispose]() {
FILE: pool/mod.ts
type CreatePoolClientOptions (line 8) | interface CreatePoolClientOptions {
function createPoolClient (line 13) | function createPoolClient(
FILE: pool/pool.ts
type Pool (line 1) | interface Pool<T extends Disposable> {
FILE: protocol/deno_streams/command.ts
function writeCommand (line 8) | async function writeCommand(
function sendCommand (line 17) | async function sendCommand(
function sendCommands (line 29) | async function sendCommands(
FILE: protocol/deno_streams/mod.ts
class Protocol (line 9) | class Protocol implements BaseProtocol {
method constructor (line 13) | constructor(conn: Deno.Conn) {
method sendCommand (line 18) | sendCommand(
method readReply (line 32) | readReply(returnsUint8Arrays?: boolean): Promise<RedisReply> {
method writeCommand (line 36) | async writeCommand(command: Command): Promise<void> {
method pipeline (line 41) | pipeline(commands: Command[]): Promise<Array<RedisReply | ErrorReplyEr...
FILE: protocol/deno_streams/reply.ts
function readReply (line 23) | async function readReply(
function readIntegerReply (line 76) | async function readIntegerReply(
function readBulkReply (line 87) | function readBulkReply(
function readVerbatimStringReply (line 94) | function readVerbatimStringReply(
function readSimpleStringReply (line 101) | function readSimpleStringReply(
function readArrayReply (line 108) | function readArrayReply(
function readPushReply (line 115) | function readPushReply(
function readArrayLikeReply (line 122) | async function readArrayLikeReply(
function readSetReply (line 147) | async function readSetReply(
function readMapReply (line 172) | async function readMapReply(
function readAttributeReply (line 196) | async function readAttributeReply(
function readBooleanReply (line 214) | async function readBooleanReply(reader: BufReader): Promise<1 | 0> {
function readDoubleReply (line 225) | function readDoubleReply(
function readBigNumberReply (line 232) | function readBigNumberReply(
function readNullReply (line 239) | async function readNullReply(reader: BufReader): Promise<null> {
function readSingleLineReply (line 245) | async function readSingleLineReply(
type BlobLikeReply (line 262) | type BlobLikeReply = string | types.Binary | null;
function readBlobReply (line 263) | async function readBlobReply(
function parseErrorReplyOrFail (line 288) | function parseErrorReplyOrFail(line: Uint8Array): never {
function readErrorReplyOrFail (line 296) | async function readErrorReplyOrFail(reader: BufReader): Promise<never> {
function readLine (line 304) | async function readLine(reader: BufReader): Promise<Uint8Array> {
function parseSize (line 325) | function parseSize(line: Uint8Array): number {
FILE: protocol/shared/command.ts
constant CRLF (line 6) | const CRLF = encoder.encode("\r\n");
function encodeCommand (line 13) | function encodeCommand(
function writeFrom (line 70) | function writeFrom(
function encodeCommands (line 79) | function encodeCommands(commands: Array<Command>): Uint8Array {
FILE: protocol/shared/protocol.ts
type Command (line 4) | interface Command {
type Protocol (line 10) | interface Protocol {
FILE: protocol/shared/types.ts
type RedisValue (line 7) | type RedisValue = string | number | Uint8Array;
type SimpleString (line 12) | type SimpleString = string;
type Integer (line 17) | type Integer = number;
type Bulk (line 22) | type Bulk = BulkString | BulkNil;
type BulkString (line 27) | type BulkString = string;
type BulkNil (line 32) | type BulkNil = null;
type Raw (line 37) | type Raw = SimpleString | Integer | Bulk | ConditionalArray | Binary;
type Binary (line 39) | type Binary = Uint8Array;
type ConditionalArray (line 44) | type ConditionalArray = Raw[];
type RedisReply (line 46) | type RedisReply = Raw | ConditionalArray;
type RawOrError (line 48) | type RawOrError = Raw | ErrorReplyError;
type Protover (line 52) | type Protover = 2 | 3;
FILE: protocol/web_streams/command.ts
function writeCommand (line 7) | async function writeCommand(
function sendCommand (line 21) | async function sendCommand(
type Command (line 32) | interface Command {
function sendCommands (line 38) | async function sendCommands(
FILE: protocol/web_streams/mod.ts
class Protocol (line 8) | class Protocol implements BaseProtocol {
method constructor (line 11) | constructor(conn: Deno.Conn) {
method sendCommand (line 15) | sendCommand(
method readReply (line 29) | readReply(returnsUint8Arrays?: boolean): Promise<RedisReply> {
method writeCommand (line 33) | writeCommand(command: Command): Promise<void> {
method pipeline (line 37) | pipeline(commands: Command[]): Promise<Array<RedisReply | ErrorReplyEr...
FILE: protocol/web_streams/reply.ts
function readReply (line 23) | async function readReply(
FILE: protocol/web_streams/reply_test.ts
function createReadableByteStream (line 99) | function createReadableByteStream(payload: string): ReadableStream<Uint8...
FILE: redis.ts
type Redis (line 112) | interface Redis
class RedisImpl (line 131) | class RedisImpl implements Redis {
method isClosed (line 134) | get isClosed() {
method isConnected (line 138) | get isConnected() {
method constructor (line 142) | constructor(client: Client) {
method addEventListener (line 146) | addEventListener<K extends ConnectionEventType>(
method removeEventListener (line 158) | removeEventListener<K extends ConnectionEventType>(
method sendCommand (line 170) | sendCommand(
method connect (line 178) | connect(): Promise<void> {
method close (line 182) | close(): void {
method execReply (line 190) | async execReply<T extends Raw = Raw>(
method execStatusReply (line 201) | async execStatusReply(
method execIntegerReply (line 209) | async execIntegerReply(
method execBinaryReply (line 217) | async execBinaryReply(
method execBulkReply (line 229) | async execBulkReply<T extends Bulk = Bulk>(
method execArrayReply (line 237) | async execArrayReply<T extends Raw = Raw>(
method execIntegerOrNilReply (line 245) | async execIntegerOrNilReply(
method execStatusOrNilReply (line 253) | async execStatusOrNilReply(
method aclCat (line 261) | aclCat(categoryname?: string) {
method aclDelUser (line 268) | aclDelUser(...usernames: string[]) {
method aclGenPass (line 272) | aclGenPass(bits?: number) {
method aclGetUser (line 279) | aclGetUser(username: string) {
method aclHelp (line 287) | aclHelp() {
method aclList (line 291) | aclList() {
method aclLoad (line 295) | aclLoad() {
method aclLog (line 301) | aclLog(param: number | ACLLogMode) {
method aclSave (line 308) | aclSave() {
method aclSetUser (line 312) | aclSetUser(username: string, ...rules: string[]) {
method aclUsers (line 316) | aclUsers() {
method aclWhoami (line 320) | aclWhoami() {
method append (line 324) | append(key: string, value: RedisValue) {
method auth (line 328) | auth(param1: RedisValue, param2?: RedisValue) {
method bgrewriteaof (line 335) | bgrewriteaof() {
method bgsave (line 339) | bgsave() {
method bitcount (line 343) | bitcount(key: string, start?: number, end?: number) {
method bitfield (line 350) | bitfield(
method bitop (line 373) | bitop(operation: string, destkey: string, ...keys: string[]) {
method bitpos (line 377) | bitpos(key: string, bit: number, start?: number, end?: number) {
method blpop (line 387) | blpop(timeout: number, ...keys: string[]) {
method brpop (line 393) | brpop(timeout: number, ...keys: string[]) {
method brpoplpush (line 399) | brpoplpush(source: string, destination: string, timeout: number) {
method bzpopmin (line 403) | bzpopmin(timeout: number, ...keys: string[]) {
method bzpopmax (line 409) | bzpopmax(timeout: number, ...keys: string[]) {
method clientCaching (line 415) | clientCaching(mode: ClientCachingMode) {
method clientGetName (line 419) | clientGetName() {
method clientGetRedir (line 423) | clientGetRedir() {
method clientID (line 427) | clientID() {
method clientInfo (line 431) | clientInfo() {
method clientKill (line 435) | clientKill(opts: ClientKillOpts) {
method clientList (line 458) | clientList(opts?: ClientListOpts) {
method clientPause (line 471) | clientPause(timeout: number, mode?: ClientPauseMode) {
method clientSetName (line 478) | clientSetName(connectionName: string) {
method clientTracking (line 482) | clientTracking(opts: ClientTrackingOpts) {
method clientTrackingInfo (line 508) | clientTrackingInfo() {
method clientUnblock (line 512) | clientUnblock(
method clientUnpause (line 522) | clientUnpause(): Promise<SimpleString> {
method asking (line 526) | asking() {
method clusterAddSlots (line 530) | clusterAddSlots(...slots: number[]) {
method clusterCountFailureReports (line 534) | clusterCountFailureReports(nodeId: string) {
method clusterCountKeysInSlot (line 538) | clusterCountKeysInSlot(slot: number) {
method clusterDelSlots (line 542) | clusterDelSlots(...slots: number[]) {
method clusterFailover (line 546) | clusterFailover(mode?: ClusterFailoverMode) {
method clusterFlushSlots (line 553) | clusterFlushSlots() {
method clusterForget (line 557) | clusterForget(nodeId: string) {
method clusterGetKeysInSlot (line 561) | clusterGetKeysInSlot(slot: number, count: number) {
method clusterInfo (line 570) | clusterInfo() {
method clusterKeySlot (line 574) | clusterKeySlot(key: string) {
method clusterMeet (line 578) | clusterMeet(ip: string, port: number) {
method clusterMyID (line 582) | clusterMyID() {
method clusterNodes (line 586) | clusterNodes() {
method clusterReplicas (line 590) | clusterReplicas(nodeId: string) {
method clusterReplicate (line 594) | clusterReplicate(nodeId: string) {
method clusterReset (line 598) | clusterReset(mode?: ClusterResetMode) {
method clusterSaveConfig (line 605) | clusterSaveConfig() {
method clusterSetSlot (line 609) | clusterSetSlot(
method clusterSlaves (line 626) | clusterSlaves(nodeId: string) {
method clusterSlots (line 630) | clusterSlots() {
method command (line 634) | command() {
method commandCount (line 640) | commandCount() {
method commandGetKeys (line 644) | commandGetKeys() {
method commandInfo (line 648) | commandInfo(...commandNames: string[]) {
method configGet (line 657) | configGet(parameter: string) {
method configResetStat (line 661) | configResetStat() {
method configRewrite (line 665) | configRewrite() {
method configSet (line 669) | configSet(parameter: string, value: string | number) {
method dbsize (line 673) | dbsize() {
method debugObject (line 677) | debugObject(key: string) {
method debugSegfault (line 681) | debugSegfault() {
method decr (line 685) | decr(key: string) {
method decrby (line 689) | decrby(key: string, decrement: number) {
method del (line 693) | del(...keys: string[]) {
method discard (line 697) | discard() {
method dump (line 701) | dump(key: string) {
method echo (line 705) | echo(message: RedisValue) {
method eval (line 709) | eval(script: string, keys: string[], args: string[]) {
method evalsha (line 719) | evalsha(sha1: string, keys: string[], args: string[]) {
method exec (line 729) | exec() {
method exists (line 733) | exists(...keys: string[]) {
method expire (line 737) | expire(key: string, seconds: number) {
method expireat (line 741) | expireat(key: string, timestamp: string) {
method flushall (line 745) | flushall(async?: boolean) {
method flushdb (line 752) | flushdb(async?: boolean) {
method geoadd (line 760) | geoadd(key: string, ...params: any[]) {
method geohash (line 774) | geohash(key: string, ...members: string[]) {
method geopos (line 778) | geopos(key: string, ...members: string[]) {
method geodist (line 784) | geodist(
method georadius (line 796) | georadius(
method georadiusbymember (line 811) | georadiusbymember(
method pushGeoRadiusOpts (line 822) | private pushGeoRadiusOpts(
method get (line 850) | get(key: string) {
method getbit (line 854) | getbit(key: string, offset: number) {
method getrange (line 858) | getrange(key: string, start: number, end: number) {
method getset (line 862) | getset(key: string, value: RedisValue) {
method hdel (line 866) | hdel(key: string, ...fields: string[]) {
method hexists (line 870) | hexists(key: string, field: string) {
method hget (line 874) | hget(key: string, field: string) {
method hgetall (line 878) | hgetall(key: string) {
method hincrby (line 882) | hincrby(key: string, field: string, increment: number) {
method hincrbyfloat (line 886) | hincrbyfloat(key: string, field: string, increment: number) {
method hkeys (line 895) | hkeys(key: string) {
method hlen (line 899) | hlen(key: string) {
method hmget (line 903) | hmget(key: string, ...fields: string[]) {
method hmset (line 908) | hmset(key: string, ...params: any[]) {
method hset (line 923) | hset(key: string, ...params: any[]) {
method hsetnx (line 937) | hsetnx(key: string, field: string, value: RedisValue) {
method hstrlen (line 941) | hstrlen(key: string, field: string) {
method hvals (line 945) | hvals(key: string) {
method incr (line 949) | incr(key: string) {
method incrby (line 953) | incrby(key: string, increment: number) {
method incrbyfloat (line 957) | incrbyfloat(key: string, increment: number) {
method info (line 961) | info(section?: string) {
method keys (line 968) | keys(pattern: string) {
method lastsave (line 972) | lastsave() {
method lindex (line 976) | lindex(key: string, index: number) {
method linsert (line 980) | linsert(key: string, loc: LInsertLocation, pivot: string, value: Redis...
method llen (line 984) | llen(key: string) {
method lpop (line 990) | lpop(key: string, count?: number): Promise<Bulk | Array<BulkString>> {
method lpos (line 1010) | lpos(
method lpush (line 1033) | lpush(key: string, ...elements: RedisValue[]) {
method lpushx (line 1037) | lpushx(key: string, ...elements: RedisValue[]) {
method lrange (line 1041) | lrange(key: string, start: number, stop: number) {
method lrem (line 1045) | lrem(key: string, count: number, element: string | number) {
method lset (line 1049) | lset(key: string, index: number, element: string | number) {
method ltrim (line 1053) | ltrim(key: string, start: number, stop: number) {
method memoryDoctor (line 1057) | memoryDoctor() {
method memoryHelp (line 1061) | memoryHelp() {
method memoryMallocStats (line 1065) | memoryMallocStats() {
method memoryPurge (line 1069) | memoryPurge() {
method memoryStats (line 1073) | memoryStats() {
method memoryUsage (line 1077) | memoryUsage(key: string, opts?: MemoryUsageOpts) {
method mget (line 1085) | mget(...keys: string[]) {
method migrate (line 1089) | migrate(
method moduleList (line 1113) | moduleList() {
method moduleLoad (line 1117) | moduleLoad(path: string, ...args: string[]) {
method moduleUnload (line 1121) | moduleUnload(name: string) {
method monitor (line 1125) | monitor() {
method move (line 1129) | move(key: string, db: string) {
method mset (line 1134) | mset(...params: any[]) {
method msetnx (line 1149) | msetnx(...params: any[]) {
method multi (line 1163) | multi() {
method objectEncoding (line 1167) | objectEncoding(key: string) {
method objectFreq (line 1171) | objectFreq(key: string) {
method objectHelp (line 1175) | objectHelp() {
method objectIdletime (line 1179) | objectIdletime(key: string) {
method objectRefCount (line 1183) | objectRefCount(key: string) {
method persist (line 1187) | persist(key: string) {
method pexpire (line 1191) | pexpire(key: string, milliseconds: number) {
method pexpireat (line 1195) | pexpireat(key: string, millisecondsTimestamp: number) {
method pfadd (line 1199) | pfadd(key: string, ...elements: string[]) {
method pfcount (line 1203) | pfcount(...keys: string[]) {
method pfmerge (line 1207) | pfmerge(destkey: string, ...sourcekeys: string[]) {
method ping (line 1211) | ping(message?: RedisValue) {
method psetex (line 1218) | psetex(key: string, milliseconds: number, value: RedisValue) {
method publish (line 1222) | publish(channel: string, message: string) {
method subscribe (line 1228) | async subscribe<TMessage extends string | string[] = string>(
method psubscribe (line 1243) | async psubscribe<TMessage extends string | string[] = string>(
method pubsubChannels (line 1258) | pubsubChannels(pattern?: string) {
method pubsubNumpat (line 1265) | pubsubNumpat() {
method pubsubNumsub (line 1269) | pubsubNumsub(...channels: string[]) {
method pttl (line 1277) | pttl(key: string) {
method quit (line 1281) | quit() {
method randomkey (line 1285) | randomkey() {
method readonly (line 1289) | readonly() {
method readwrite (line 1293) | readwrite() {
method rename (line 1297) | rename(key: string, newkey: string) {
method renamenx (line 1301) | renamenx(key: string, newkey: string) {
method restore (line 1305) | restore(
method role (line 1327) | role() {
method rpop (line 1335) | rpop(key: string) {
method rpoplpush (line 1339) | rpoplpush(source: string, destination: string) {
method rpush (line 1343) | rpush(key: string, ...elements: RedisValue[]) {
method rpushx (line 1347) | rpushx(key: string, ...elements: RedisValue[]) {
method sadd (line 1351) | sadd(key: string, ...members: string[]) {
method save (line 1355) | save() {
method scard (line 1359) | scard(key: string) {
method scriptDebug (line 1363) | scriptDebug(mode: ScriptDebugMode) {
method scriptExists (line 1367) | scriptExists(...sha1s: string[]) {
method scriptFlush (line 1371) | scriptFlush() {
method scriptKill (line 1375) | scriptKill() {
method scriptLoad (line 1379) | scriptLoad(script: string) {
method sdiff (line 1383) | sdiff(...keys: string[]) {
method sdiffstore (line 1387) | sdiffstore(destination: string, ...keys: string[]) {
method select (line 1391) | select(index: number) {
method hello (line 1395) | hello(opts?: HelloOpts): Promise<ConditionalArray> {
method set (line 1409) | set<TSetOpts extends SetOpts | SetWithModeOpts = SetOpts>(
method setbit (line 1449) | setbit(key: string, offset: number, value: RedisValue) {
method setex (line 1453) | setex(key: string, seconds: number, value: RedisValue) {
method setnx (line 1457) | setnx(key: string, value: RedisValue) {
method setrange (line 1461) | setrange(key: string, offset: number, value: RedisValue) {
method shutdown (line 1465) | shutdown(mode?: ShutdownMode) {
method sinter (line 1472) | sinter(...keys: string[]) {
method sinterstore (line 1476) | sinterstore(destination: string, ...keys: string[]) {
method sismember (line 1480) | sismember(key: string, member: string) {
method slaveof (line 1484) | slaveof(host: string, port: number) {
method slaveofNoOne (line 1488) | slaveofNoOne() {
method replicaof (line 1492) | replicaof(host: string, port: number) {
method replicaofNoOne (line 1496) | replicaofNoOne() {
method slowlog (line 1500) | slowlog(subcommand: string, ...args: string[]) {
method smembers (line 1504) | smembers(key: string) {
method smove (line 1508) | smove(source: string, destination: string, member: string) {
method sort (line 1520) | sort(
method spop (line 1549) | spop(key: string, count?: number) {
method srandmember (line 1558) | srandmember(key: string, count?: number) {
method srem (line 1565) | srem(key: string, ...members: string[]) {
method stralgo (line 1614) | stralgo(
method strlen (line 1645) | strlen(key: string) {
method sunion (line 1649) | sunion(...keys: string[]) {
method sunionstore (line 1653) | sunionstore(destination: string, ...keys: string[]) {
method swapdb (line 1657) | swapdb(index1: number, index2: number) {
method sync (line 1661) | sync() {
method time (line 1665) | time() {
method touch (line 1669) | touch(...keys: string[]) {
method ttl (line 1673) | ttl(key: string) {
method type (line 1677) | type(key: string) {
method unlink (line 1681) | unlink(...keys: string[]) {
method unwatch (line 1685) | unwatch() {
method wait (line 1689) | wait(numreplicas: number, timeout: number) {
method watch (line 1693) | watch(...keys: string[]) {
method xack (line 1697) | xack(key: string, group: string, ...xids: XIdInput[]) {
method xadd (line 1706) | xadd(
method xclaim (line 1742) | xclaim(key: string, opts: XClaimOpts, ...xids: XIdInput[]) {
method xdel (line 1798) | xdel(key: string, ...xids: XIdInput[]) {
method xlen (line 1806) | xlen(key: string) {
method xgroupCreate (line 1810) | xgroupCreate(
method xgroupDelConsumer (line 1831) | xgroupDelConsumer(
method xgroupDestroy (line 1845) | xgroupDestroy(key: string, groupName: string) {
method xgroupHelp (line 1849) | xgroupHelp() {
method xgroupSetID (line 1853) | xgroupSetID(
method xinfoStream (line 1867) | xinfoStream(key: string) {
method xinfoStreamFull (line 1897) | xinfoStreamFull(key: string, count?: number) {
method xinfoGroups (line 1931) | xinfoGroups(key: string) {
method xinfoConsumers (line 1948) | xinfoConsumers(key: string, group: string) {
method xpending (line 1967) | xpending(
method xpendingCount (line 1989) | xpendingCount(
method xrange (line 2008) | xrange(
method xrevrange (line 2024) | xrevrange(
method xread (line 2040) | xread(
method xreadgroup (line 2078) | xreadgroup(
method xtrim (line 2120) | xtrim(key: string, maxlen: XMaxlen) {
method zadd (line 2147) | zadd(
method pushZAddOpts (line 2173) | private pushZAddOpts(
method zaddIncr (line 2194) | zaddIncr(
method zcard (line 2206) | zcard(key: string) {
method zcount (line 2210) | zcount(key: string, min: number, max: number) {
method zincrby (line 2214) | zincrby(key: string, increment: number, member: string) {
method zinter (line 2218) | zinter(
method zinterstore (line 2229) | zinterstore(
method zunionstore (line 2238) | zunionstore(
method pushZStoreArgs (line 2247) | private pushZStoreArgs(
method zlexcount (line 2274) | zlexcount(key: string, min: string, max: string) {
method zpopmax (line 2278) | zpopmax(key: string, count?: number) {
method zpopmin (line 2285) | zpopmin(key: string, count?: number) {
method zrange (line 2292) | zrange(
method zrangebylex (line 2302) | zrangebylex(
method zrangebyscore (line 2312) | zrangebyscore(
method zrank (line 2322) | zrank(key: string, member: string) {
method zrem (line 2326) | zrem(key: string, ...members: string[]) {
method zremrangebylex (line 2330) | zremrangebylex(key: string, min: string, max: string) {
method zremrangebyrank (line 2334) | zremrangebyrank(key: string, start: number, stop: number) {
method zremrangebyscore (line 2338) | zremrangebyscore(key: string, min: number | string, max: number | stri...
method zrevrange (line 2342) | zrevrange(
method zrevrangebylex (line 2352) | zrevrangebylex(
method zrevrangebyscore (line 2362) | zrevrangebyscore(
method pushZRangeOpts (line 2372) | private pushZRangeOpts(
method zrevrank (line 2389) | zrevrank(key: string, member: string) {
method zscore (line 2393) | zscore(key: string, member: string) {
method scan (line 2397) | scan(
method sscan (line 2407) | sscan(
method hscan (line 2418) | hscan(
method zscan (line 2429) | zscan(
method pushScanOpts (line 2440) | private pushScanOpts(
method latencyDoctor (line 2456) | latencyDoctor() {
method tx (line 2460) | tx() {
method pipeline (line 2464) | pipeline() {
method [Symbol.dispose] (line 186) | [Symbol.dispose](): void {
type RedisConnectOptions (line 2469) | interface RedisConnectOptions extends RedisConnectionOptions {
function connect (line 2484) | async function connect(options: RedisConnectOptions): Promise<Redis> {
function createLazyClient (line 2504) | function createLazyClient(options: RedisConnectOptions): Redis {
function create (line 2516) | function create(client: Client): Redis {
function parseURL (line 2531) | function parseURL(url: string): RedisConnectOptions {
function createBaseLazyClient (line 2556) | function createBaseLazyClient(connection: Connection): Client {
FILE: stream.ts
type XId (line 7) | interface XId {
type XMessage (line 12) | interface XMessage {
type XKeyId (line 17) | interface XKeyId {
type XKeyIdLike (line 22) | type XKeyIdLike = [string, XIdInput];
type XKeyIdGroup (line 24) | interface XKeyIdGroup {
type XKeyIdGroupLike (line 29) | type XKeyIdGroupLike = [string, XIdGroupRead];
type XReadStream (line 31) | type XReadStream = { key: string; messages: XMessage[] };
type XReadReply (line 32) | type XReadReply = XReadStream[];
type XReadIdData (line 35) | type XReadIdData = [string, string[]];
type XReadStreamRaw (line 36) | type XReadStreamRaw = [string, XReadIdData[]];
type XReadReplyRaw (line 37) | type XReadReplyRaw = XReadStreamRaw[];
type XIdInput (line 50) | type XIdInput = XId | [number, number] | number | string;
type XIdAdd (line 54) | type XIdAdd = XIdInput | "*";
type XIdGroupRead (line 60) | type XIdGroupRead = XIdInput | ">";
type XIdPos (line 63) | type XIdPos = XIdInput | "+";
type XIdNeg (line 65) | type XIdNeg = XIdInput | "-";
type XIdCreateGroup (line 67) | type XIdCreateGroup = XIdInput | "$";
type XAddFieldValues (line 69) | type XAddFieldValues =
type XReadOpts (line 73) | interface XReadOpts {
type XReadGroupOpts (line 78) | interface XReadGroupOpts {
type XMaxlen (line 85) | interface XMaxlen {
type XClaimReply (line 90) | type XClaimReply = XClaimMessages | XClaimJustXId;
type XClaimMessages (line 91) | interface XClaimMessages {
type XClaimJustXId (line 95) | interface XClaimJustXId {
type XPendingReply (line 108) | interface XPendingReply {
type XPendingConsumer (line 114) | interface XPendingConsumer {
type XPendingCount (line 130) | interface XPendingCount {
type StartEndCount (line 139) | interface StartEndCount {
type XInfoStreamReply (line 145) | interface XInfoStreamReply {
type XInfoStreamFullReply (line 155) | interface XInfoStreamFullReply {
type XGroupDetail (line 167) | interface XGroupDetail {
type XConsumerDetail (line 175) | interface XConsumerDetail {
type XInfoConsumersReply (line 182) | type XInfoConsumersReply = XInfoConsumer[];
type XInfoConsumer (line 190) | interface XInfoConsumer {
type XInfoGroupsReply (line 197) | type XInfoGroupsReply = XInfoGroup[];
type XInfoGroup (line 198) | interface XInfoGroup {
type XClaimOpts (line 205) | interface XClaimOpts {
function parseXMessage (line 216) | function parseXMessage(raw: XReadIdData): XMessage {
function convertMap (line 233) | function convertMap(raw: ConditionalArray): Map<string, Raw> {
function parseXReadReply (line 250) | function parseXReadReply(raw: XReadReplyRaw): XReadReply {
function parseXId (line 263) | function parseXId(raw: string): XId {
function parseXPendingConsumers (line 268) | function parseXPendingConsumers(
function parseXPendingCounts (line 282) | function parseXPendingCounts(raw: ConditionalArray): XPendingCount[] {
function parseXGroupDetail (line 304) | function parseXGroupDetail(rawGroups: ConditionalArray): XGroupDetail[] {
function parseXConsumerDetail (line 329) | function parseXConsumerDetail(nestedRaws: Raw[][]): XConsumerDetail[] {
function xidstr (line 358) | function xidstr(
function rawnum (line 368) | function rawnum(raw: Raw): number {
function rawstr (line 371) | function rawstr(raw: Raw): string {
function isString (line 375) | function isString(x: any): x is string {
function isNumber (line 380) | function isNumber(x: any): x is number {
function isCondArray (line 384) | function isCondArray(x: Raw): x is ConditionalArray {
function isXId (line 390) | function isXId(xid: XIdAdd): xid is XId {
FILE: subscription.ts
type DefaultPubSubMessageType (line 2) | type DefaultPubSubMessageType = string;
type PubSubMessageType (line 3) | type PubSubMessageType = string | string[];
type SubscribeCommand (line 4) | type SubscribeCommand = "SUBSCRIBE" | "PSUBSCRIBE";
type RedisPubSubMessage (line 6) | interface RedisPubSubMessage<TMessage = DefaultPubSubMessageType> {
type RedisSubscription (line 12) | interface RedisSubscription<
FILE: tests/cluster/test.ts
method newRedis (line 70) | async newRedis(opts) {
method newRedis (line 128) | async newRedis(opts) {
method newRedis (line 183) | async newRedis(opts) {
FILE: tests/cluster/test_util.ts
type TestCluster (line 11) | interface TestCluster {
function startRedisCluster (line 15) | async function startRedisCluster(ports: number[]): Promise<TestCluster> {
function stopRedisCluster (line 56) | async function stopRedisCluster(cluster: TestCluster): Promise<void> {
function nextPorts (line 62) | function nextPorts(n: number): Array<number> {
FILE: tests/commands/acl.ts
function aclTests (line 11) | function aclTests(
FILE: tests/commands/connection.ts
function connectionTests (line 15) | function connectionTests(
function parseCommandStats (line 253) | function parseCommandStats(
FILE: tests/commands/general.ts
function generalTests (line 13) | function generalTests(
FILE: tests/commands/geo.ts
function geoTests (line 7) | function geoTests(
FILE: tests/commands/hash.ts
function hashTests (line 6) | function hashTests(
FILE: tests/commands/hyper_loglog.ts
function hyperloglogTests (line 6) | function hyperloglogTests(
FILE: tests/commands/key.ts
function keyTests (line 10) | function keyTests(
FILE: tests/commands/latency.ts
function latencyTests (line 6) | function latencyTests(
FILE: tests/commands/list.ts
function listTests (line 12) | function listTests(
FILE: tests/commands/pipeline.ts
function pipelineTests (line 7) | function pipelineTests(
FILE: tests/commands/pubsub.ts
function pubsubTests (line 7) | function pubsubTests(
FILE: tests/commands/resp3.ts
function resp3Tests (line 12) | function resp3Tests(
FILE: tests/commands/script.ts
function scriptTests (line 6) | function scriptTests(
FILE: tests/commands/set.ts
function setTests (line 10) | function setTests(
FILE: tests/commands/sorted_set.ts
function zsetTests (line 14) | function zsetTests(
FILE: tests/commands/stream.ts
function streamTests (line 14) | function streamTests(
FILE: tests/commands/string.ts
function stringTests (line 22) | function stringTests(
FILE: tests/test_util.ts
type Connector (line 5) | type Connector = typeof connect;
type Logger (line 6) | interface Logger {
type TestServer (line 9) | interface TestServer {
function startRedis (line 19) | async function startRedis({
function stopRedis (line 65) | async function stopRedis(server: TestServer): Promise<void> {
function ensureTerminated (line 78) | async function ensureTerminated(
function newClient (line 95) | function newClient(opt: RedisConnectOptions): Promise<Redis> {
function exists (line 99) | async function exists(path: string): Promise<boolean> {
function nextPort (line 112) | function nextPort(): number {
function waitForPort (line 116) | async function waitForPort(port: number): Promise<void> {
function tempPath (line 134) | function tempPath(fileName: string): string {
function usesRedisVersion (line 139) | function usesRedisVersion(version: "6" | "7" | "8"): boolean {
function withTimeout (line 145) | function withTimeout(
FILE: tools/format-benchmark-results.js
function formatResultsAsMarkdown (line 4) | function formatResultsAsMarkdown({ name, results }) {
function makeTableRow (line 27) | function makeTableRow(columns) {
Condensed preview — 106 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (391K chars).
[
{
"path": ".denov",
"chars": 6,
"preview": "2.7.7\n"
},
{
"path": ".editorconfig",
"chars": 43,
"preview": "[*.ts]\nindent_style = space\nindent_size = 2"
},
{
"path": ".github/CODEOWNERS",
"chars": 10,
"preview": "* @uki00a\n"
},
{
"path": ".github/FUNDING.yml",
"chars": 642,
"preview": "# These are supported funding model platforms\n\ngithub: [keroxp]\npatreon: # Replace with a single Patreon username\nopen_c"
},
{
"path": ".github/dependabot.yml",
"chars": 155,
"preview": "version: 2\nupdates:\n - package-ecosystem: \"github-actions\"\n directory: \"/\"\n schedule:\n interval: \"monthly\"\n "
},
{
"path": ".github/pinact.yaml",
"chars": 125,
"preview": "# pinact - https://github.com/suzuki-shunsuke/pinact\nfiles:\n - pattern: \"^\\\\.github/workflows/.*\\\\.ya?ml$\"\n\nignore_acti"
},
{
"path": ".github/workflows/build.yml",
"chars": 3421,
"preview": "name: CI\n\non:\n push:\n branches:\n - \"**\"\n pull_request:\n branches:\n - \"**\"\n\njobs:\n test:\n runs-on: "
},
{
"path": ".github/workflows/publish.yml",
"chars": 645,
"preview": "name: Publish to JSR\non:\n release:\n types: [published]\n workflow_dispatch:\n\njobs:\n publish:\n runs-on: ubuntu-la"
},
{
"path": ".gitignore",
"chars": 99,
"preview": "deno.d.ts\n.idea\ntestdata/*/\nbenchmark/node_modules\ntests/tmp/*/\ncoverage\ntmp\n.vscode/settings.json\n"
},
{
"path": ".octocov.yml",
"chars": 459,
"preview": "# generated by octocov init\ncodeToTestRatio:\n code:\n - \"**/*.ts\"\n - \"!tests/*.ts\"\n test:\n - \"tests/*.ts\"\ncove"
},
{
"path": "LICENSE",
"chars": 1071,
"preview": "MIT License\n\nCopyright (c) 2018 Yusuke Sakurai\n\nPermission is hereby granted, free of charge, to any person obtaining a "
},
{
"path": "README.md",
"chars": 5981,
"preview": "# deno-redis\n\n[](https://jsr.io/@db/redis)\n[: numb"
},
{
"path": "benchmark/.npmrc",
"chars": 38,
"preview": "min-release-age=1\nignore-scripts=true\n"
},
{
"path": "benchmark/benchmark.js",
"chars": 1828,
"preview": "import { add, complete, configure, cycle, save, suite } from \"benny\";\nimport { dirname, join } from \"node:path\";\n\nexport"
},
{
"path": "benchmark/deno-redis.ts",
"chars": 706,
"preview": "import { run } from \"./benchmark.js\";\nimport { connect } from \"../mod.ts\";\nimport { connect as connectWebStreams } from "
},
{
"path": "benchmark/ioredis.js",
"chars": 218,
"preview": "import { run } from \"./benchmark.js\";\nimport Redis from \"ioredis\";\n\nconst redis = new Redis();\nredis.on(\"connect\", async"
},
{
"path": "benchmark/package.json",
"chars": 249,
"preview": "{\n \"name\": \"deno-redis-benchmark\",\n \"version\": \"0.0.0\",\n \"type\": \"module\",\n \"description\": \"\",\n \"keywords\": [],\n \""
},
{
"path": "client.ts",
"chars": 897,
"preview": "import type { Connection, SendCommandOptions } from \"./connection.ts\";\nimport type { RedisReply, RedisValue } from \"./pr"
},
{
"path": "command.ts",
"chars": 43384,
"preview": "import type {\n Binary,\n Bulk,\n BulkNil,\n BulkString,\n ConditionalArray,\n Integer,\n Protover,\n Raw,\n RedisValue,"
},
{
"path": "connection.ts",
"chars": 2590,
"preview": "import type { Backoff } from \"./backoff.ts\";\nimport type { ConnectionEventMap } from \"./events.ts\";\nimport type { ErrorR"
},
{
"path": "default_client.ts",
"chars": 1523,
"preview": "import type { Client } from \"./client.ts\";\nimport type {\n DefaultPubSubMessageType,\n PubSubMessageType,\n RedisSubscri"
},
{
"path": "default_connection.ts",
"chars": 12824,
"preview": "import type { Backoff } from \"./backoff.ts\";\nimport { exponentialBackoff } from \"./backoff.ts\";\nimport type {\n Connecti"
},
{
"path": "default_subscription.ts",
"chars": 3686,
"preview": "import { decoder } from \"./internal/encoding.ts\";\nimport {\n kUnstableStartReadLoop,\n kUnstableWriteCommand,\n} from \"./"
},
{
"path": "deno.json",
"chars": 2466,
"preview": "{\n \"name\": \"@db/redis\",\n \"version\": \"0.41.0\",\n \"exports\": {\n \".\": \"./mod.ts\",\n \"./experimental/pool\": \"./experi"
},
{
"path": "deps/cluster-key-slot.js",
"chars": 71,
"preview": "export { default as calculateSlot } from \"npm:cluster-key-slot@1.1.0\";\n"
},
{
"path": "deps/std/assert.ts",
"chars": 36,
"preview": "export * from \"jsr:@std/assert@^1\";\n"
},
{
"path": "deps/std/async.ts",
"chars": 41,
"preview": "export * from \"jsr:@std/async@^1/delay\";\n"
},
{
"path": "deps/std/bytes.ts",
"chars": 42,
"preview": "export * from \"jsr:@std/bytes@^1/concat\";\n"
},
{
"path": "deps/std/collections.ts",
"chars": 66,
"preview": "export { distinctBy } from \"jsr:@std/collections@^1/distinct-by\";\n"
},
{
"path": "deps/std/io.ts",
"chars": 264,
"preview": "export { BufReader } from \"jsr:@std/io@0.224.5/buf-reader\";\nexport { BufWriter } from \"jsr:@std/io@0.224.5/buf-writer\";\n"
},
{
"path": "deps/std/random.ts",
"chars": 112,
"preview": "export { sample } from \"jsr:@std/random@0.1.0/sample\";\nexport { shuffle } from \"jsr:@std/random@0.1.0/shuffle\";\n"
},
{
"path": "deps/std/testing.ts",
"chars": 145,
"preview": "export * from \"jsr:@std/testing@^1/bdd\";\nexport type * from \"jsr:@std/testing@^1/types\";\nexport { assertType } from \"jsr"
},
{
"path": "errors.ts",
"chars": 934,
"preview": "export class EOFError extends Error {}\n\nexport class ConnectionClosedError extends Error {}\n\nexport class SubscriptionCl"
},
{
"path": "events.ts",
"chars": 509,
"preview": "export type ConnectionEvent = Record<string, unknown>;\n\nexport type ConnectionErrorEventDetails = {\n error: unknown;\n};"
},
{
"path": "executor.ts",
"chars": 133,
"preview": "import type { Client } from \"./client.ts\";\n\n/** @deprecated Use {@linkcode Client} instead. */\nexport type CommandExecut"
},
{
"path": "experimental/README.md",
"chars": 209,
"preview": "# Experimental features\n\ndeno-redis has some experimental features:\n\n- [Redis Cluster client](cluster/README.md)\n\n**Thes"
},
{
"path": "experimental/cluster/README.md",
"chars": 661,
"preview": "# experimental/cluster\n\n[](https://doc.deno.land/https/deno.land/x/redis/exp"
},
{
"path": "experimental/cluster/mod.ts",
"chars": 10959,
"preview": "/**\n * @module\n * @experimental **NOTE**: This is an unstable module.\n *\n * Based on https://github.com/antirez/redis-rb"
},
{
"path": "experimental/pool/mod.ts",
"chars": 35,
"preview": "export * from \"../../pool/mod.ts\";\n"
},
{
"path": "experimental/web_streams_connection/mod.ts",
"chars": 564,
"preview": "/**\n * @module\n * @experimental **NOTE**: This is an unstable module.\n */\nimport { kUnstableCreateProtocol } from \"../.."
},
{
"path": "import_map.dev.json",
"chars": 121,
"preview": "{\n \"imports\": {\n \"benny\": \"npm:benny@3.7.1\",\n \"@uki00a/deno-json-lint\": \"jsr:@uki00a/deno-json-lint@^0.4.0\"\n }\n}"
},
{
"path": "import_map.test.json",
"chars": 168,
"preview": "{\n \"imports\": {\n \"https://deno.land/x/redis/mod.ts\": \"./mod.ts\",\n \"https://deno.land/x/redis/experimental/cluster"
},
{
"path": "internal/buffered_readable_stream.ts",
"chars": 2048,
"preview": "import { concateBytes } from \"./concate_bytes.ts\";\n\nconst LF = \"\\n\".charCodeAt(0);\n/**\n * Wraps `ReadableStream` to prov"
},
{
"path": "internal/buffered_readable_stream_test.ts",
"chars": 2938,
"preview": "import { encoder } from \"./encoding.ts\";\nimport { assertEquals, assertRejects } from \"../deps/std/assert.ts\";\nimport { B"
},
{
"path": "internal/concate_bytes.ts",
"chars": 219,
"preview": "export function concateBytes(a: Uint8Array, b: Uint8Array) {\n if (a.length === 0) return b;\n const size = a.length + b"
},
{
"path": "internal/concate_bytes_test.ts",
"chars": 490,
"preview": "import { assertEquals } from \"../deps/std/assert.ts\";\nimport { concateBytes } from \"./concate_bytes.ts\";\n\nDeno.test(\"con"
},
{
"path": "internal/delegate.ts",
"chars": 725,
"preview": "type Delegate<TObject, TMethods extends keyof TObject> =\n Pick<TObject, TMethods> extends Record<\n string | symbol,\n"
},
{
"path": "internal/delegate_test.ts",
"chars": 1409,
"preview": "import { delegate } from \"./delegate.ts\";\nimport {\n assert,\n assertExists,\n assertFalse,\n assertNotStrictEquals,\n} f"
},
{
"path": "internal/encoding.ts",
"chars": 84,
"preview": "export const encoder = new TextEncoder();\nexport const decoder = new TextDecoder();\n"
},
{
"path": "internal/on.ts",
"chars": 1762,
"preview": "interface Options {\n signal?: AbortSignal;\n}\n/**\n * Converts {@linkcode EventTarget} to {@linkcode AsyncIterableIterato"
},
{
"path": "internal/on_test.ts",
"chars": 1834,
"preview": "import { on } from \"./on.ts\";\nimport {\n assert,\n assertEquals,\n assertStrictEquals,\n} from \"../deps/std/assert.ts\";\n\n"
},
{
"path": "internal/symbols.ts",
"chars": 541,
"preview": "/**\n * @private\n */\nexport const kUnstableReadReply = Symbol(\"deno-redis.readReply\");\n\n/**\n * @private\n */\nexport const "
},
{
"path": "internal/typed_event_target.ts",
"chars": 1097,
"preview": "export interface TypedEventTarget<TEventMap extends Record<string, unknown>>\n extends\n Omit<\n EventTarget,\n "
},
{
"path": "mod.ts",
"chars": 2568,
"preview": "// Generated by tools/make_mod.ts. Don't edit.\nexport { okReply } from \"./protocol/shared/types.ts\";\nexport { connect, c"
},
{
"path": "pipeline.ts",
"chars": 2929,
"preview": "import type { Connection, SendCommandOptions } from \"./connection.ts\";\nimport { kEmptyRedisArgs } from \"./protocol/share"
},
{
"path": "pool/client.ts",
"chars": 2964,
"preview": "import type { Connection, SendCommandOptions } from \"../connection.ts\";\nimport type { Pool } from \"./pool.ts\";\nimport ty"
},
{
"path": "pool/default_pool.ts",
"chars": 2583,
"preview": "import type { Pool } from \"./pool.ts\";\n\nclass AlreadyRemovedFromPoolError extends Error {\n constructor() {\n super(\"T"
},
{
"path": "pool/default_pool_test.ts",
"chars": 2415,
"preview": "import { assert, assertEquals, assertRejects } from \"../deps/std/assert.ts\";\nimport { createDefaultPool } from \"./defaul"
},
{
"path": "pool/mod.ts",
"chars": 1013,
"preview": "import type { Redis, RedisConnectOptions } from \"../redis.ts\";\nimport { create } from \"../redis.ts\";\nimport type { Conne"
},
{
"path": "pool/pool.ts",
"chars": 136,
"preview": "export interface Pool<T extends Disposable> {\n acquire(signal?: AbortSignal): Promise<T>;\n release(item: T): void;\n c"
},
{
"path": "protocol/deno_streams/command.ts",
"chars": 1428,
"preview": "import type { BufReader, BufWriter } from \"../../deps/std/io.ts\";\nimport { readReply } from \"./reply.ts\";\nimport { Error"
},
{
"path": "protocol/deno_streams/mod.ts",
"chars": 1287,
"preview": "import { BufReader, BufWriter } from \"../../deps/std/io.ts\";\nimport { readReply } from \"./reply.ts\";\nimport { sendComman"
},
{
"path": "protocol/deno_streams/reply.ts",
"chars": 8606,
"preview": "import type { BufReader } from \"../../deps/std/io.ts\";\nimport type * as types from \"../shared/types.ts\";\nimport {\n Arra"
},
{
"path": "protocol/deno_streams/reply_test.ts",
"chars": 2118,
"preview": "import { assertEquals, assertRejects } from \"../../deps/std/assert.ts\";\nimport { BufReader, readerFromStreamReader } fro"
},
{
"path": "protocol/shared/command.ts",
"chars": 2817,
"preview": "import { concat } from \"../../deps/std/bytes.ts\";\nimport { encoder } from \"../../internal/encoding.ts\";\nimport type { Re"
},
{
"path": "protocol/shared/command_test.ts",
"chars": 391,
"preview": "import { encodeCommand } from \"./command.ts\";\nimport { assertEquals } from \"../../deps/std/assert.ts\";\n\nDeno.test({\n na"
},
{
"path": "protocol/shared/protocol.ts",
"chars": 577,
"preview": "import type { RedisReply, RedisValue } from \"./types.ts\";\nimport type { ErrorReplyError } from \"../../errors.ts\";\n\nexpor"
},
{
"path": "protocol/shared/reply.ts",
"chars": 1489,
"preview": "/**\n * Represents a number in RESP2/RESP3.\n */\nexport const IntegerReplyCode = \":\".charCodeAt(0);\n/**\n * Represents a do"
},
{
"path": "protocol/shared/types.ts",
"chars": 1274,
"preview": "import type { ErrorReplyError } from \"../../errors.ts\";\n\n/**\n * @see https://redis.io/topics/protocol\n */\n\nexport type R"
},
{
"path": "protocol/web_streams/command.ts",
"chars": 1726,
"preview": "import { readReply } from \"./reply.ts\";\nimport { ErrorReplyError } from \"../../errors.ts\";\nimport type { BufferedReadabl"
},
{
"path": "protocol/web_streams/mod.ts",
"chars": 1337,
"preview": "import { sendCommand, sendCommands, writeCommand } from \"./command.ts\";\nimport { readReply } from \"./reply.ts\";\nimport t"
},
{
"path": "protocol/web_streams/reply.ts",
"chars": 4239,
"preview": "import type * as types from \"../shared/types.ts\";\nimport {\n ArrayReplyCode,\n AttributeReplyCode,\n BigNumberReplyCode,"
},
{
"path": "protocol/web_streams/reply_test.ts",
"chars": 4130,
"preview": "import { assertEquals, assertRejects } from \"../../deps/std/assert.ts\";\nimport { readReply } from \"./reply.ts\";\nimport {"
},
{
"path": "redis.ts",
"chars": 64386,
"preview": "import type {\n ACLLogMode,\n BitfieldOpts,\n BitfieldWithOverflowOpts,\n ClientCachingMode,\n ClientKillOpts,\n ClientL"
},
{
"path": "stream.ts",
"chars": 9678,
"preview": "import type {\n ConditionalArray,\n Raw,\n RedisValue,\n} from \"./protocol/shared/types.ts\";\n\nexport interface XId {\n un"
},
{
"path": "subscription.ts",
"chars": 855,
"preview": "import type { Binary } from \"./protocol/shared/types.ts\";\nexport type DefaultPubSubMessageType = string;\nexport type Pub"
},
{
"path": "tests/backoff_test.ts",
"chars": 674,
"preview": "import { assertEquals } from \"../deps/std/assert.ts\";\nimport { describe, it } from \"../deps/std/testing.ts\";\n\nimport { e"
},
{
"path": "tests/client_test.ts",
"chars": 6021,
"preview": "import type { Redis } from \"../redis.ts\";\nimport { delay } from \"../deps/std/async.ts\";\nimport {\n assert,\n assertEqual"
},
{
"path": "tests/cluster/test.ts",
"chars": 7261,
"preview": "import { nextPorts, startRedisCluster, stopRedisCluster } from \"./test_util.ts\";\nimport type { TestCluster } from \"./tes"
},
{
"path": "tests/cluster/test_util.ts",
"chars": 1650,
"preview": "import {\n ensureTerminated,\n nextPort,\n startRedis,\n stopRedis,\n} from \"../test_util.ts\";\nimport type { TestServer }"
},
{
"path": "tests/cluster_test.ts",
"chars": 3935,
"preview": "import type { Redis } from \"../mod.ts\";\nimport {\n assert,\n assertEquals,\n assertStringIncludes,\n} from \"../deps/std/a"
},
{
"path": "tests/commands/acl.ts",
"chars": 4657,
"preview": "import {\n assertArrayIncludes,\n assertEquals,\n assertStringIncludes,\n} from \"../../deps/std/assert.ts\";\nimport { afte"
},
{
"path": "tests/commands/connection.ts",
"chars": 7813,
"preview": "import { createLazyClient } from \"../../mod.ts\";\nimport {\n assert,\n assertArrayIncludes,\n assertEquals,\n assertExist"
},
{
"path": "tests/commands/general.ts",
"chars": 6111,
"preview": "import { ErrorReplyError } from \"../../mod.ts\";\nimport type { Redis } from \"../../mod.ts\";\nimport { assertEquals, assert"
},
{
"path": "tests/commands/geo.ts",
"chars": 2577,
"preview": "import { assertEquals } from \"../../deps/std/assert.ts\";\nimport { afterAll, beforeAll, it } from \"../../deps/std/testing"
},
{
"path": "tests/commands/hash.ts",
"chars": 3285,
"preview": "import { assertEquals } from \"../../deps/std/assert.ts\";\nimport { afterAll, beforeAll, beforeEach, it } from \"../../deps"
},
{
"path": "tests/commands/hyper_loglog.ts",
"chars": 969,
"preview": "import { assertEquals } from \"../../deps/std/assert.ts\";\nimport { afterAll, beforeAll, it } from \"../../deps/std/testing"
},
{
"path": "tests/commands/key.ts",
"chars": 6005,
"preview": "import {\n assert,\n assertArrayIncludes,\n assertEquals,\n} from \"../../deps/std/assert.ts\";\nimport { afterAll, beforeAl"
},
{
"path": "tests/commands/latency.ts",
"chars": 831,
"preview": "import { assertStringIncludes } from \"../../deps/std/assert.ts\";\nimport { afterAll, beforeAll, describe, it } from \"../."
},
{
"path": "tests/commands/list.ts",
"chars": 4396,
"preview": "import { assertEquals } from \"../../deps/std/assert.ts\";\nimport {\n afterAll,\n beforeAll,\n beforeEach,\n describe,\n i"
},
{
"path": "tests/commands/pipeline.ts",
"chars": 4174,
"preview": "import type { Raw } from \"../../mod.ts\";\nimport { ErrorReplyError } from \"../../mod.ts\";\nimport { assert, assertEquals }"
},
{
"path": "tests/commands/pubsub.ts",
"chars": 7644,
"preview": "import { delay } from \"../../deps/std/async.ts\";\nimport { assert, assertEquals, assertRejects } from \"../../deps/std/ass"
},
{
"path": "tests/commands/resp3.ts",
"chars": 3108,
"preview": "import {\n assert,\n assertArrayIncludes,\n assertEquals,\n assertStrictEquals,\n} from \"../../deps/std/assert.ts\";\nimpor"
},
{
"path": "tests/commands/script.ts",
"chars": 1285,
"preview": "import { assert, assertEquals } from \"../../deps/std/assert.ts\";\nimport { afterAll, afterEach, beforeAll, it } from \"../"
},
{
"path": "tests/commands/set.ts",
"chars": 3376,
"preview": "import {\n assert,\n assertArrayIncludes,\n assertEquals,\n} from \"../../deps/std/assert.ts\";\nimport { afterAll, beforeAl"
},
{
"path": "tests/commands/sorted_set.ts",
"chars": 10301,
"preview": "import { assert, assertEquals } from \"../../deps/std/assert.ts\";\nimport type { IsExact } from \"../../deps/std/testing.ts"
},
{
"path": "tests/commands/stream.ts",
"chars": 22602,
"preview": "import type { Redis } from \"../../mod.ts\";\nimport { ErrorReplyError } from \"../../mod.ts\";\nimport { parseXId } from \"../"
},
{
"path": "tests/commands/string.ts",
"chars": 10036,
"preview": "import {\n assertEquals,\n assertGreater,\n assertLessOrEqual,\n} from \"../../deps/std/assert.ts\";\nimport type { IsExact,"
},
{
"path": "tests/commands_test.ts",
"chars": 2844,
"preview": "import { nextPort, startRedis, stopRedis } from \"./test_util.ts\";\nimport type { TestServer } from \"./test_util.ts\";\nimpo"
},
{
"path": "tests/pool_test.ts",
"chars": 2054,
"preview": "import type {\n DefaultPubSubMessageType,\n RedisSubscription,\n} from \"../subscription.ts\";\nimport { createPoolClient } "
},
{
"path": "tests/reconnect_test.ts",
"chars": 1987,
"preview": "import {\n assert,\n assertEquals,\n assertInstanceOf,\n assertStringIncludes,\n} from \"../deps/std/assert.ts\";\nimport { "
},
{
"path": "tests/server/redis.conf",
"chars": 92,
"preview": "# Base configuration\ndaemonize no\nappendonly yes\ncluster-node-timeout 30000\nmaxclients 1001\n"
},
{
"path": "tests/test_util.ts",
"chars": 3968,
"preview": "import type { Redis, RedisConnectOptions } from \"../mod.ts\";\nimport { connect } from \"../mod.ts\";\nimport { delay } from "
},
{
"path": "tests/util_test.ts",
"chars": 1875,
"preview": "import { parseURL } from \"../mod.ts\";\nimport { assertEquals } from \"../deps/std/assert.ts\";\nimport { describe, it } from"
},
{
"path": "tools/format-benchmark-results.js",
"chars": 1100,
"preview": "// deno-lint-ignore-file no-console -- This file is a script, not a library module.\nimport { join } from \"node:path\";\n\nf"
}
]
About this extraction
This page contains the full source code of the denodrivers/redis GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 106 files (357.0 KB), approximately 97.8k tokens, and a symbol index with 662 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.