Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
92e3f34
test(resp2-resp3): expand coverage and consolidate spec cleanup
nkaradzhov Apr 15, 2026
261f28f
feat(client): default connections to RESP3
nkaradzhov Apr 7, 2026
8c222bd
fix(compat): normalize RESP2/RESP3 reply transforms
nkaradzhov Apr 7, 2026
e5dcaae
fix(modules): address targeted RESP compatibility regressions
nkaradzhov Apr 7, 2026
5b18a00
tests: fix some wrong assertions
nkaradzhov Apr 15, 2026
2ede56c
docs: add migration doc
nkaradzhov Apr 16, 2026
3c84ae3
test: stabilize generic-transformers spec across Node versions
nkaradzhov Apr 16, 2026
cccfe57
fix merge and lint issues
nkaradzhov May 13, 2026
c905730
address PR comments
nkaradzhov May 14, 2026
9b6696e
refactor(client): introduce DEFAULT_RESP for runtime fallbacks
nkaradzhov May 14, 2026
91084aa
fix(client): forward typeMapping through stream message transforms
nkaradzhov May 14, 2026
9b47f14
fix(search): forward typeMapping through composed RESP3 transforms
nkaradzhov May 15, 2026
d815010
fix(search): make toCompatObject properties writable
nkaradzhov May 15, 2026
0e91538
fix(client): apply DOUBLE typeMapping in GEOSEARCH_WITH on RESP3
nkaradzhov May 15, 2026
a4f0390
fix(client): harden HOTKEYS GET reply parsing
nkaradzhov May 15, 2026
aef2399
fix(client): MODULE LIST preserves unknown keys and avoids undefined …
nkaradzhov May 15, 2026
f013889
types(time-series): replace `never` metadata slot in MRANGE SELECTED_…
nkaradzhov May 15, 2026
6bf1b88
fix(search): avoid "[object Object]" warnings from FT.HYBRID
nkaradzhov May 15, 2026
9c8648c
fix(client): normalize stream name in Resp3Compat Array branch and do…
nkaradzhov May 15, 2026
d49fa6f
docs(migration): call out legacy callback mode now defaulting to RESP3
nkaradzhov May 18, 2026
75604f1
docs(migration): add XRANGE/XREVRANGE/XCLAIM/XAUTOCLAIM to prototype …
nkaradzhov May 18, 2026
856f101
docs(migration): note removal of unstableResp3/unstableResp3Modules
nkaradzhov May 18, 2026
b88f72a
docs(migration): document maintNotifications default flip to "auto" o…
nkaradzhov May 18, 2026
a495521
types(client): narrow DEFAULT_RESP from RespVersions to literal 3
nkaradzhov May 18, 2026
08577e3
test(client): use DEFAULT_RESP in HELLO spec instead of hardcoded 3
nkaradzhov May 18, 2026
deb1d0f
refactor(client): centralize map-like reply primitives in reply-utils
nkaradzhov May 18, 2026
51aa7e2
test(client): add unit tests for map-like reply primitives
nkaradzhov May 18, 2026
5110f79
style(search): drop unused eslint-disable for no-unused-vars
nkaradzhov May 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions docs/v5-to-v6.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# v5 to v6 migration guide

## RESP3 is now the default protocol

In v5, Node-Redis defaulted to `RESP: 2` unless you explicitly configured `RESP: 3`.
In v6, the default is now `RESP: 3`.

RESP3 introduces a bunch of “on the wire” formats that replace RESP2 workarounds.
Node-Redis already maps most of the RESP2 workarounds to the proper javascript type, but there are some commands that were missed.
Those are now aligned to return the proper types. For more details on protocol type mapping, see [RESP type mapping](./RESP.md).


## Default behavior changes (v5 default -> v6 default)

- `GEOSEARCH_WITH`, `GEORADIUS_WITH`, `GEORADIUS_RO_WITH`, `GEORADIUSBYMEMBER_WITH`, `GEORADIUSBYMEMBER_RO_WITH` - `distance`, `coordinates.longitude`, and `coordinates.latitude` are now `number` (previously `string`).
- `CF.INSERTNX` changed from `Array<boolean>` to `Array<number>`.


## Stabilized APIs
In v5, some command transforms were unstable under RESP3. In v6, those commands are stabilized and normalized:
These stabilization changes are RESP3-only: RESP2 transforms are unchanged.
They are breaking only for clients using RESP3 (including v5 users who explicitly opted into RESP3, and v6 users on the new default RESP3).

| Package | Command | Return type change | Notes |
|---|---|---|---|
| `@redis/client` | `HOTKEYS GET` | `ReplyUnion -> HotkeysGetReply \| null` | RESP3 reply now normalized to stable structured output. |
| `@redis/client` | `XREAD` | `ReplyUnion -> StreamsMessagesReply \| null` | RESP3 reply is normalized to v4/v5-compatible stream list shape. |
| `@redis/client` | `XREADGROUP` | `ReplyUnion -> StreamsMessagesReply \| null` | RESP3 reply is normalized to v4/v5-compatible stream list shape. |
| `@redis/search` | `FT.AGGREGATE` | `ReplyUnion -> AggregateReply` | RESP3 map/array variants normalized to aggregate reply shape. |
| `@redis/search` | `FT.AGGREGATE WITHCURSOR` | `ReplyUnion -> AggregateWithCursorReply` | Cursor + results are normalized for RESP3. |
| `@redis/search` | `FT.CURSOR READ` | `ReplyUnion -> AggregateWithCursorReply` | RESP3 cursor-read map/array wrapper variants are normalized to a stable `{ total, results, cursor }` reply shape. |
| `@redis/search` | `FT.SEARCH` | `ReplyUnion -> SearchReply` | RESP3 map-like payload normalized to `{ total, documents }`. |
| `@redis/search` | `FT.SEARCH NOCONTENT` | `ReplyUnion -> SearchNoContentReply` | RESP3 normalized through `FT.SEARCH` then projected to ids. |
| `@redis/search` | `FT.SPELLCHECK` | `ReplyUnion -> SpellCheckReply` | RESP3 result/suggestion map variants normalized. |
| `@redis/search` | `FT.HYBRID` | `ReplyUnion -> HybridSearchResult` | RESP3 map-like payload normalized to hybrid result object. |
| `@redis/search` | `FT.INFO` | `ReplyUnion -> InfoReply` | RESP3 map-like payload normalized to stable info object shape. |
| `@redis/search` | `FT.PROFILE SEARCH` | `ReplyUnion -> ProfileReplyResp2` | RESP3 profile/results wrappers normalized (Redis 7.4/8 layouts). |
| `@redis/search` | `FT.PROFILE AGGREGATE` | `ReplyUnion -> ProfileReplyResp2` | RESP3 profile/results wrappers normalized (Redis 7.4/8 layouts). |
| `@redis/time-series` | `TS.INFO` | `ReplyUnion -> InfoReply` | RESP3 map/array variants normalized to `InfoReply`. |
| `@redis/time-series` | `TS.INFO DEBUG` | `ReplyUnion -> InfoDebugReply` | RESP3 `keySelfName`/`chunks` payload normalized. |
| `@redis/time-series` | `TS.MRANGE GROUPBY` | `{ sources: Array<string>; samples: Array<{ timestamp: number; value: number }> } -> { samples: Array<{ timestamp: number; value: number }> }` | `sources` removed from RESP3 grouped reply. |
| `@redis/time-series` | `TS.MREVRANGE GROUPBY` | `{ sources: Array<string>; samples: Array<{ timestamp: number; value: number }> } -> { samples: Array<{ timestamp: number; value: number }> }` | In RESP3 grouped reverse-range replies, `sources` is removed and output now includes only `{ samples }`. |
| `@redis/time-series` | `TS.MRANGE SELECTED_LABELS GROUPBY` | `{ labels: Record<string, string \| null>; sources: Array<string>; samples: Array<{ timestamp: number; value: number }> } -> { labels: Record<string, string \| null>; samples: Array<{ timestamp: number; value: number }> }` | `sources` removed from RESP3 selected-labels grouped reply. |
| `@redis/time-series` | `TS.MREVRANGE SELECTED_LABELS GROUPBY` | `{ labels: Record<string, string \| null>; sources: Array<string>; samples: Array<{ timestamp: number; value: number }> } -> { labels: Record<string, string \| null>; samples: Array<{ timestamp: number; value: number }> }` | In RESP3 selected-labels grouped reverse-range replies, `sources` is removed and output now includes `{ labels, samples }`. |

## Object Prototype Normalization
In v6, object-like replies are normalized to plain objects (`{}` / `Object.defineProperties({}, ...)`) instead of null-prototype objects (`Object.create(null)`).

Compatibility impact: this can be technically breaking for code/tests that assert a `null` prototype (for example `Object.getPrototypeOf(reply) === null` or deep-equality against `Object.create(null)`), but for most users key access/iteration/serialization behavior remains the same.

Commands affected:

- `@redis/client`: `CONFIG GET`, `FUNCTION STATS`, `HGETALL`, `LATENCY HISTOGRAM` (`histogram_usec`), `PUBSUB NUMSUB`, `PUBSUB SHARDNUMSUB`, `VINFO`, `VLINKS WITHSCORES`, `XINFO STREAM` (entry message objects), `XREAD`/`XREADGROUP` (message objects), `XRANGE`/`XREVRANGE` (message objects), `XCLAIM`/`XAUTOCLAIM` (message objects)
- `@redis/search`: `FT.AGGREGATE`, `FT.AGGREGATE WITHCURSOR`, `FT.CURSOR READ`, `FT.CONFIG GET`, `FT.HYBRID`, `FT.INFO`, `FT.SEARCH`, `FT.PROFILE SEARCH`, `FT.PROFILE AGGREGATE`
- `@redis/time-series`: `TS.MGET`, `TS.MGET WITHLABELS`, `TS.MGET SELECTED_LABELS`, `TS.MRANGE`, `TS.MREVRANGE`, `TS.MRANGE GROUPBY`, `TS.MREVRANGE GROUPBY`, `TS.MRANGE WITHLABELS`, `TS.MREVRANGE WITHLABELS`, `TS.MRANGE WITHLABELS GROUPBY`, `TS.MREVRANGE WITHLABELS GROUPBY`, `TS.MRANGE SELECTED_LABELS`, `TS.MREVRANGE SELECTED_LABELS`, `TS.MRANGE SELECTED_LABELS GROUPBY`, `TS.MREVRANGE SELECTED_LABELS GROUPBY`
- `@redis/bloom`: `BF.INFO`, `CF.INFO`, `CMS.INFO`, `TOPK.INFO`, `TDIGEST.INFO`

Additionally, RESP3 map decoding now creates plain objects by default, so commands that expose raw RESP3 maps as JS objects inherit the same prototype change.


## `unstableResp3` and `unstableResp3Modules` are removed

Both options were v5 gates for opt-in access to in-development RESP3 transforms. In v6 those transforms are stable (see the [Stabilized APIs](#stabilized-apis) table above) and the gates have been deleted. If your v5 code passed either option, remove the property — TypeScript will surface it as an unknown-property error on the options literal:

```javascript
// v5
const client = createClient({ RESP: 3, unstableResp3: true });

// v6
const client = createClient({ RESP: 3 });
```


## `maintNotifications` default is now derived from `RESP`

In v5, `maintNotifications` defaulted to `"disabled"` regardless of protocol. In v6, the default is derived from the negotiated RESP version: `"auto"` when `RESP: 3` (the new default), and `"disabled"` when `RESP: 2`. Because v6 also flips the protocol default to RESP3, clients that don't set `maintNotifications` explicitly now opt into Enterprise maintenance push notifications, the relaxed socket/command timeouts, and endpoint-type negotiation — behavior v5 never enabled by default.

`"auto"` is the functional on-switch for maintenance handling; it is not a no-op. The client subscribes to maintenance push frames (moving/migrating/failing-over) and adjusts socket/command timeouts during maintenance windows. On non-Enterprise servers this is generally harmless, but it does change topology-change behavior versus v5.

To keep the v5 default, set `maintNotifications: "disabled"` explicitly (or pin `RESP: 2`):

```javascript
// Keep v5 maintenance semantics on RESP3
const client = createClient({ maintNotifications: "disabled" });

// Or pin RESP2 and inherit the disabled default
const client = createClient({ RESP: 2 });
```


## Legacy (callback) mode now uses RESP3

`createClient().legacy()` reads the parent client's RESP version. With the v6 default of RESP3, legacy callback consumers will see RESP3-shaped replies for any command whose transforms differ between protocol versions (for example, doubles arriving as `number` instead of `string`, or hash-like replies arriving as `Map`s). To keep the v5 callback reply shapes, pin `RESP: 2` on the parent client:

```javascript
const legacy = createClient({ RESP: 2 }).legacy();
```


## If you need to preserve v5 default behavior while migrating, pin RESP2 explicitly:

```javascript
// Single node
const client = createClient({ RESP: 2 });

// Cluster
const cluster = createCluster({ RESP: 2, ...});

// Sentinel
const sentinel = createSentinel({ RESP: 2, ... });

// Pool
const pool = createClientPool({ RESP: 2 });
```
35 changes: 0 additions & 35 deletions docs/v5.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,41 +40,6 @@ This replaces the previous approach of using `commandOptions({ returnBuffers: tr

RESP3 uses a different mechanism for handling Pub/Sub messages. Instead of modifying the `onReply` handler as in RESP2, RESP3 provides a dedicated `onPush` handler. When using RESP3, the client automatically uses this more efficient push notification system.

## Known Limitations

### Unstable Commands

Some Redis commands have unstable RESP3 transformations. These commands will throw an error when used with RESP3 unless you explicitly opt in to using them by setting `unstableResp3: true` in your client configuration:

```javascript
const client = createClient({
RESP: 3,
unstableResp3: true
});
```

The following commands have unstable RESP3 implementations:

1. **Stream Commands**:
- `XREAD` and `XREADGROUP` - The response format differs between RESP2 and RESP3

2. **Search Commands (RediSearch)**:
- `FT.AGGREGATE`
- `FT.AGGREGATE_WITHCURSOR`
- `FT.CURSOR_READ`
- `FT.INFO`
- `FT.PROFILE_AGGREGATE`
- `FT.PROFILE_SEARCH`
- `FT.SEARCH`
- `FT.SEARCH_NOCONTENT`
- `FT.SPELLCHECK`

3. **Time Series Commands**:
- `TS.INFO`
- `TS.INFO_DEBUG`

If you need to use these commands with RESP3, be aware that the response format might change in future versions.

# Sentinel Support

[Sentinel](./sentinel.md)
Expand Down
3 changes: 2 additions & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ export default [
varsIgnorePattern: '^_'
}],
'@typescript-eslint/no-empty-object-type': ['error', {
allowObjectTypes: 'always'
allowObjectTypes: 'always',
allowInterfaces: 'with-single-extends'
}]
}
},
Expand Down
9 changes: 9 additions & 0 deletions packages/bloom/lib/commands/bloom/EXISTS.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,13 @@ describe('BF.EXISTS', () => {
false
);
}, GLOBAL.SERVERS.OPEN);

testUtils.testWithClient('client.bf.exists with existing item', async client => {
await client.bf.add('key', 'item');

assert.strictEqual(
await client.bf.exists('key', 'item'),
true
);
}, GLOBAL.SERVERS.OPEN);
});
21 changes: 21 additions & 0 deletions packages/bloom/lib/commands/bloom/INFO.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,25 @@ describe('BF.INFO', () => {
assert.equal(typeof reply['Number of items inserted'], 'number');
assert.equal(typeof reply['Expansion rate'], 'number');
}, GLOBAL.SERVERS.OPEN);

testUtils.testWithClient('client.bf.info - structural shape assertion', async client => {
await client.bf.reserve('key', 0.01, 100);
const reply = await client.bf.info('key');

// Assert the exact RESP2 response structure (object with specific keys)
// This would break if RESP3 returns a different shape
assert.ok(reply !== null && typeof reply === 'object');
assert.ok(!Array.isArray(reply));
assert.ok(!(reply instanceof Map));
assert.ok('Capacity' in reply);
assert.ok('Size' in reply);
assert.ok('Number of filters' in reply);
assert.ok('Number of items inserted' in reply);
assert.ok('Expansion rate' in reply);
assert.equal(reply['Capacity'], 100);
assert.equal(typeof reply['Size'], 'number');
assert.equal(typeof reply['Number of filters'], 'number');
assert.equal(typeof reply['Number of items inserted'], 'number');
assert.equal(typeof reply['Expansion rate'], 'number');
}, GLOBAL.SERVERS.OPEN);
});
11 changes: 11 additions & 0 deletions packages/bloom/lib/commands/bloom/MEXISTS.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,15 @@ describe('BF.MEXISTS', () => {
[false, false]
);
}, GLOBAL.SERVERS.OPEN);

testUtils.testWithClient('client.bf.mExists with existing items', async client => {
const key = 'mExistsKey';
await client.bf.add(key, 'item1');
await client.bf.add(key, 'item2');

assert.deepEqual(
await client.bf.mExists(key, ['item1', 'item2', 'item3']),
[true, true, false]
);
}, GLOBAL.SERVERS.OPEN);
});
17 changes: 9 additions & 8 deletions packages/bloom/lib/commands/bloom/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
import { RESP_TYPES, TypeMapping } from "@redis/client";

export function transformInfoV2Reply<T>(reply: Array<any>, typeMapping?: TypeMapping): T {
export function transformInfoV2Reply<T>(reply: Array<unknown>, typeMapping?: TypeMapping): T {
const entries = reply as Array<{ toString(): string }>;
const mapType = typeMapping ? typeMapping[RESP_TYPES.MAP] : undefined;

switch (mapType) {
case Array: {
return reply as unknown as T;
}
case Map: {
const ret = new Map<string, any>();
const ret = new Map<string, unknown>();

for (let i = 0; i < reply.length; i += 2) {
ret.set(reply[i].toString(), reply[i + 1]);
for (let i = 0; i < entries.length; i += 2) {
ret.set(entries[i].toString(), entries[i + 1]);
}

return ret as unknown as T;
}
default: {
const ret = Object.create(null);
const ret: Record<string, unknown> = {};

for (let i = 0; i < reply.length; i += 2) {
ret[reply[i].toString()] = reply[i + 1];
for (let i = 0; i < entries.length; i += 2) {
ret[entries[i].toString()] = entries[i + 1];
}

return ret as unknown as T;
}
}
}
}
2 changes: 1 addition & 1 deletion packages/bloom/lib/commands/count-min-sketch/INFO.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ describe('CMS.INFO', () => {
client.cms.info('key')
]);

const expected = Object.create(null);
const expected = {};
expected['width'] = width;
expected['depth'] = depth;
expected['count'] = 0;
Expand Down
8 changes: 8 additions & 0 deletions packages/bloom/lib/commands/cuckoo/ADDNX.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,12 @@ describe('CF.ADDNX', () => {
true
);
}, GLOBAL.SERVERS.OPEN);

testUtils.testWithClient('client.cf.addNX returns false when item already exists', async client => {
await client.cf.addNX('key', 'item');
assert.equal(
await client.cf.addNX('key', 'item'),
false
);
}, GLOBAL.SERVERS.OPEN);
});
9 changes: 9 additions & 0 deletions packages/bloom/lib/commands/cuckoo/DEL.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,13 @@ describe('CF.DEL', () => {

assert.equal(reply, false);
}, GLOBAL.SERVERS.OPEN);

testUtils.testWithClient('client.cf.del with existing item', async client => {
await client.cf.reserve('key', 4);
await client.cf.add('key', 'item');

const reply = await client.cf.del('key', 'item');

assert.equal(reply, true);
}, GLOBAL.SERVERS.OPEN);
});
9 changes: 9 additions & 0 deletions packages/bloom/lib/commands/cuckoo/EXISTS.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,13 @@ describe('CF.EXISTS', () => {
false
);
}, GLOBAL.SERVERS.OPEN);

testUtils.testWithClient('client.cf.exists with existing item', async client => {
await client.cf.reserve('key', 100);
await client.cf.add('key', 'item');
assert.equal(
await client.cf.exists('key', 'item'),
true
);
}, GLOBAL.SERVERS.OPEN);
});
28 changes: 28 additions & 0 deletions packages/bloom/lib/commands/cuckoo/INFO.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,32 @@ describe('CF.INFO', () => {
assert.equal(typeof reply['Expansion rate'], 'number');
assert.equal(typeof reply['Max iterations'], 'number');
}, GLOBAL.SERVERS.OPEN);

testUtils.testWithClient('client.cf.info returns object structure', async client => {
await client.cf.reserve('key', 4);
const reply = await client.cf.info('key');

// Structural assertion: response must be a plain object (not an array)
assert.ok(!Array.isArray(reply), 'reply should not be an array');
assert.equal(typeof reply, 'object');

// Assert exact structure with all expected keys
const expectedKeys = [
'Size',
'Number of buckets',
'Number of filters',
'Number of items inserted',
'Number of items deleted',
'Bucket size',
'Expansion rate',
'Max iterations'
];

assert.deepEqual(Object.keys(reply).sort(), expectedKeys.sort());

// Assert all values are numbers
for (const key of expectedKeys) {
assert.equal(typeof reply[key], 'number', `${key} should be a number`);
}
}, GLOBAL.SERVERS.OPEN);
});
2 changes: 1 addition & 1 deletion packages/bloom/lib/commands/cuckoo/INSERTNX.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ describe('CF.INSERTNX', () => {
testUtils.testWithClient('client.cf.insertnx', async client => {
assert.deepEqual(
await client.cf.insertNX('key', 'item'),
[true]
[1]
);
}, GLOBAL.SERVERS.OPEN);
});
4 changes: 2 additions & 2 deletions packages/bloom/lib/commands/cuckoo/INSERTNX.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Command } from '@redis/client/dist/lib/RESP/types';
import { ArrayReply, Command, NumberReply } from '@redis/client/dist/lib/RESP/types';
import INSERT, { parseCfInsertArguments } from './INSERT';

/**
Expand All @@ -16,5 +16,5 @@ export default {
args[0].push('CF.INSERTNX');
parseCfInsertArguments(...args);
},
transformReply: INSERT.transformReply
transformReply: undefined as unknown as () => ArrayReply<NumberReply<-1 | 0 | 1>>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might break the old RESP 2 responses as now it will return numbers instead of booleans for both protocols.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that is intentional, documented in v5-to-v6

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, i see what you mean. This actually breaks the RESP 2, but we know that the server is in fact returning number in both resp2 and resp3. so its more of a fix for resp2. So i would keep it like this

} as const satisfies Command;
15 changes: 15 additions & 0 deletions packages/bloom/lib/commands/t-digest/BYRANK.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,19 @@ describe('TDIGEST.BYRANK', () => {

assert.deepEqual(reply, [NaN]);
}, GLOBAL.SERVERS.OPEN);

testUtils.testWithClient('client.tDigest.byRank with data', async client => {
await client.tDigest.create('key');
await client.tDigest.add('key', [1, 2, 3, 4, 5]);

const reply = await client.tDigest.byRank('key', [0, 2, 4]);

assert.ok(Array.isArray(reply));
assert.equal(reply.length, 3);
assert.equal(typeof reply[0], 'number');
assert.equal(typeof reply[1], 'number');
assert.equal(typeof reply[2], 'number');
assert.ok(reply[0] <= reply[1]);
assert.ok(reply[1] <= reply[2]);
}, GLOBAL.SERVERS.OPEN);
});
Loading
Loading