You've already forked node-redis
mirror of
https://github.com/redis/node-redis.git
synced 2025-08-06 02:15:48 +03:00
comment cluster request & response policies (keep v4 behaver)
This commit is contained in:
@@ -105,8 +105,6 @@ createCluster({
|
|||||||
|
|
||||||
## Command Routing
|
## Command Routing
|
||||||
|
|
||||||
TODO request response policy
|
|
||||||
|
|
||||||
### Commands that operate on Redis Keys
|
### Commands that operate on Redis Keys
|
||||||
|
|
||||||
Commands such as `GET`, `SET`, etc. are routed by the first key specified. For example `MGET 1 2 3` will be routed by the key `1`.
|
Commands such as `GET`, `SET`, etc. are routed by the first key specified. For example `MGET 1 2 3` will be routed by the key `1`.
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
export { RedisModules, RedisFunctions, RedisScripts, RespVersions, TypeMapping, CommandPolicies } from './lib/RESP/types';
|
export { RedisModules, RedisFunctions, RedisScripts, RespVersions, TypeMapping/*, CommandPolicies*/ } from './lib/RESP/types';
|
||||||
export { RESP_TYPES } from './lib/RESP/decoder';
|
export { RESP_TYPES } from './lib/RESP/decoder';
|
||||||
export { VerbatimString } from './lib/RESP/verbatim-string';
|
export { VerbatimString } from './lib/RESP/verbatim-string';
|
||||||
export { defineScript } from './lib/lua-script';
|
export { defineScript } from './lib/lua-script';
|
||||||
|
@@ -200,9 +200,8 @@ export type ReplyWithTypeMapping<
|
|||||||
REPLY extends Array<infer T> ? Array<ReplyWithTypeMapping<T, TYPE_MAPPING>> :
|
REPLY extends Array<infer T> ? Array<ReplyWithTypeMapping<T, TYPE_MAPPING>> :
|
||||||
REPLY extends Set<infer T> ? Set<ReplyWithTypeMapping<T, TYPE_MAPPING>> :
|
REPLY extends Set<infer T> ? Set<ReplyWithTypeMapping<T, TYPE_MAPPING>> :
|
||||||
REPLY extends Map<infer K, infer V> ? Map<MapKey<K, TYPE_MAPPING>, ReplyWithTypeMapping<V, TYPE_MAPPING>> :
|
REPLY extends Map<infer K, infer V> ? Map<MapKey<K, TYPE_MAPPING>, ReplyWithTypeMapping<V, TYPE_MAPPING>> :
|
||||||
// `Date` & `Buffer` are supersets of `Record`, so they need to be checked first
|
// `Date | Buffer | Error` are supersets of `Record`, so they need to be checked first
|
||||||
REPLY extends Date ? REPLY :
|
REPLY extends Date | Buffer | Error ? REPLY :
|
||||||
REPLY extends Buffer ? REPLY :
|
|
||||||
REPLY extends Record<PropertyKey, any> ? {
|
REPLY extends Record<PropertyKey, any> ? {
|
||||||
[P in keyof REPLY]: ReplyWithTypeMapping<REPLY[P], TYPE_MAPPING>;
|
[P in keyof REPLY]: ReplyWithTypeMapping<REPLY[P], TYPE_MAPPING>;
|
||||||
} :
|
} :
|
||||||
@@ -222,57 +221,62 @@ export type RedisArgument = string | Buffer;
|
|||||||
|
|
||||||
export type CommandArguments = Array<RedisArgument> & { preserve?: unknown };
|
export type CommandArguments = Array<RedisArgument> & { preserve?: unknown };
|
||||||
|
|
||||||
export const REQUEST_POLICIES = {
|
// export const REQUEST_POLICIES = {
|
||||||
/**
|
// /**
|
||||||
* TODO
|
// * TODO
|
||||||
*/
|
// */
|
||||||
ALL_NODES: 'all_nodes',
|
// ALL_NODES: 'all_nodes',
|
||||||
/**
|
// /**
|
||||||
* TODO
|
// * TODO
|
||||||
*/
|
// */
|
||||||
ALL_SHARDS: 'all_shards',
|
// ALL_SHARDS: 'all_shards',
|
||||||
/**
|
// /**
|
||||||
* TODO
|
// * TODO
|
||||||
*/
|
// */
|
||||||
SPECIAL: 'special'
|
// SPECIAL: 'special'
|
||||||
} as const;
|
// } as const;
|
||||||
|
|
||||||
export type REQUEST_POLICIES = typeof REQUEST_POLICIES;
|
// export type REQUEST_POLICIES = typeof REQUEST_POLICIES;
|
||||||
|
|
||||||
export type RequestPolicies = REQUEST_POLICIES[keyof REQUEST_POLICIES];
|
// export type RequestPolicies = REQUEST_POLICIES[keyof REQUEST_POLICIES];
|
||||||
|
|
||||||
export const RESPONSE_POLICIES = {
|
// export const RESPONSE_POLICIES = {
|
||||||
/**
|
// /**
|
||||||
* TODO
|
// * TODO
|
||||||
*/
|
// */
|
||||||
ONE_SUCCEEDED: 'one_succeeded',
|
// ONE_SUCCEEDED: 'one_succeeded',
|
||||||
/**
|
// /**
|
||||||
* TODO
|
// * TODO
|
||||||
*/
|
// */
|
||||||
ALL_SUCCEEDED: 'all_succeeded',
|
// ALL_SUCCEEDED: 'all_succeeded',
|
||||||
/**
|
// /**
|
||||||
* TODO
|
// * TODO
|
||||||
*/
|
// */
|
||||||
LOGICAL_AND: 'agg_logical_and',
|
// LOGICAL_AND: 'agg_logical_and',
|
||||||
/**
|
// /**
|
||||||
* TODO
|
// * TODO
|
||||||
*/
|
// */
|
||||||
SPECIAL: 'special'
|
// SPECIAL: 'special'
|
||||||
} as const;
|
// } as const;
|
||||||
|
|
||||||
export type RESPONSE_POLICIES = typeof RESPONSE_POLICIES;
|
// export type RESPONSE_POLICIES = typeof RESPONSE_POLICIES;
|
||||||
|
|
||||||
export type ResponsePolicies = RESPONSE_POLICIES[keyof RESPONSE_POLICIES];
|
// export type ResponsePolicies = RESPONSE_POLICIES[keyof RESPONSE_POLICIES];
|
||||||
|
|
||||||
export type CommandPolicies = {
|
// export type CommandPolicies = {
|
||||||
request?: RequestPolicies | null;
|
// request?: RequestPolicies | null;
|
||||||
response?: ResponsePolicies | null;
|
// response?: ResponsePolicies | null;
|
||||||
};
|
// };
|
||||||
|
|
||||||
export type Command = {
|
export type Command = {
|
||||||
FIRST_KEY_INDEX?: number | ((this: void, ...args: Array<any>) => RedisArgument | undefined);
|
FIRST_KEY_INDEX?: number | ((this: void, ...args: Array<any>) => RedisArgument | undefined);
|
||||||
IS_READ_ONLY?: boolean;
|
IS_READ_ONLY?: boolean;
|
||||||
POLICIES?: CommandPolicies;
|
/**
|
||||||
|
* @internal
|
||||||
|
* TODO: remove once `POLICIES` is implemented
|
||||||
|
*/
|
||||||
|
IS_FORWARD_COMMAND?: boolean;
|
||||||
|
// POLICIES?: CommandPolicies;
|
||||||
transformArguments(this: void, ...args: Array<any>): CommandArguments;
|
transformArguments(this: void, ...args: Array<any>): CommandArguments;
|
||||||
TRANSFORM_LEGACY_REPLY?: boolean;
|
TRANSFORM_LEGACY_REPLY?: boolean;
|
||||||
transformReply: TransformReply | Record<RespVersions, TransformReply>;
|
transformReply: TransformReply | Record<RespVersions, TransformReply>;
|
||||||
@@ -355,32 +359,32 @@ export type CommandSignature<
|
|||||||
TYPE_MAPPING extends TypeMapping
|
TYPE_MAPPING extends TypeMapping
|
||||||
> = (...args: Parameters<COMMAND['transformArguments']>) => Promise<ReplyWithTypeMapping<CommandReply<COMMAND, RESP>, TYPE_MAPPING>>;
|
> = (...args: Parameters<COMMAND['transformArguments']>) => Promise<ReplyWithTypeMapping<CommandReply<COMMAND, RESP>, TYPE_MAPPING>>;
|
||||||
|
|
||||||
export type CommandWithPoliciesSignature<
|
// export type CommandWithPoliciesSignature<
|
||||||
COMMAND extends Command,
|
// COMMAND extends Command,
|
||||||
RESP extends RespVersions,
|
// RESP extends RespVersions,
|
||||||
TYPE_MAPPING extends TypeMapping,
|
// TYPE_MAPPING extends TypeMapping,
|
||||||
POLICIES extends CommandPolicies
|
// POLICIES extends CommandPolicies
|
||||||
> = (...args: Parameters<COMMAND['transformArguments']>) => Promise<
|
// > = (...args: Parameters<COMMAND['transformArguments']>) => Promise<
|
||||||
ReplyWithPolicy<
|
// ReplyWithPolicy<
|
||||||
ReplyWithTypeMapping<CommandReply<COMMAND, RESP>, TYPE_MAPPING>,
|
// ReplyWithTypeMapping<CommandReply<COMMAND, RESP>, TYPE_MAPPING>,
|
||||||
MergePolicies<COMMAND, POLICIES>
|
// MergePolicies<COMMAND, POLICIES>
|
||||||
>
|
// >
|
||||||
>;
|
// >;
|
||||||
|
|
||||||
export type MergePolicies<
|
// export type MergePolicies<
|
||||||
COMMAND extends Command,
|
// COMMAND extends Command,
|
||||||
POLICIES extends CommandPolicies
|
// POLICIES extends CommandPolicies
|
||||||
> = Omit<COMMAND['POLICIES'], keyof POLICIES> & POLICIES;
|
// > = Omit<COMMAND['POLICIES'], keyof POLICIES> & POLICIES;
|
||||||
|
|
||||||
type ReplyWithPolicy<
|
// type ReplyWithPolicy<
|
||||||
REPLY,
|
// REPLY,
|
||||||
POLICIES extends CommandPolicies,
|
// POLICIES extends CommandPolicies,
|
||||||
> = (
|
// > = (
|
||||||
POLICIES['request'] extends REQUEST_POLICIES['SPECIAL'] ? never :
|
// POLICIES['request'] extends REQUEST_POLICIES['SPECIAL'] ? never :
|
||||||
POLICIES['request'] extends null | undefined ? REPLY :
|
// POLICIES['request'] extends null | undefined ? REPLY :
|
||||||
unknown extends POLICIES['request'] ? REPLY :
|
// unknown extends POLICIES['request'] ? REPLY :
|
||||||
POLICIES['response'] extends RESPONSE_POLICIES['SPECIAL'] ? never :
|
// POLICIES['response'] extends RESPONSE_POLICIES['SPECIAL'] ? never :
|
||||||
POLICIES['response'] extends RESPONSE_POLICIES['ALL_SUCCEEDED' | 'ONE_SUCCEEDED' | 'LOGICAL_AND'] ? REPLY :
|
// POLICIES['response'] extends RESPONSE_POLICIES['ALL_SUCCEEDED' | 'ONE_SUCCEEDED' | 'LOGICAL_AND'] ? REPLY :
|
||||||
// otherwise, return array of replies
|
// // otherwise, return array of replies
|
||||||
Array<REPLY>
|
// Array<REPLY>
|
||||||
);
|
// );
|
||||||
|
@@ -11,15 +11,19 @@ import { once } from 'node:events';
|
|||||||
// import { promisify } from 'node:util';
|
// import { promisify } from 'node:util';
|
||||||
import { MATH_FUNCTION, loadMathFunction } from '../commands/FUNCTION_LOAD.spec';
|
import { MATH_FUNCTION, loadMathFunction } from '../commands/FUNCTION_LOAD.spec';
|
||||||
import { RESP_TYPES } from '../RESP/decoder';
|
import { RESP_TYPES } from '../RESP/decoder';
|
||||||
|
import { NumberReply } from '../RESP/types';
|
||||||
import { SortedSetMember } from '../commands/generic-transformers';
|
import { SortedSetMember } from '../commands/generic-transformers';
|
||||||
|
|
||||||
export const SQUARE_SCRIPT = defineScript({
|
export const SQUARE_SCRIPT = defineScript({
|
||||||
SCRIPT: 'return ARGV[1] * ARGV[1];',
|
SCRIPT:
|
||||||
NUMBER_OF_KEYS: 0,
|
`local number = redis.call('GET', KEYS[1])
|
||||||
transformArguments(number: number): Array<string> {
|
return number * number`,
|
||||||
return [number.toString()];
|
NUMBER_OF_KEYS: 1,
|
||||||
|
FIRST_KEY_INDEX: 0,
|
||||||
|
transformArguments(key: string) {
|
||||||
|
return [key];
|
||||||
},
|
},
|
||||||
transformReply: undefined as unknown as () => number
|
transformReply: undefined as unknown as () => NumberReply
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Client', () => {
|
describe('Client', () => {
|
||||||
@@ -214,9 +218,10 @@ describe('Client', () => {
|
|||||||
testUtils.testWithClient('with script', async client => {
|
testUtils.testWithClient('with script', async client => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
await client.multi()
|
await client.multi()
|
||||||
.square(2)
|
.set('key', '2')
|
||||||
|
.square('key')
|
||||||
.exec(),
|
.exec(),
|
||||||
[4]
|
['OK', 4]
|
||||||
);
|
);
|
||||||
}, {
|
}, {
|
||||||
...GLOBAL.SERVERS.OPEN,
|
...GLOBAL.SERVERS.OPEN,
|
||||||
@@ -280,10 +285,12 @@ describe('Client', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
testUtils.testWithClient('scripts', async client => {
|
testUtils.testWithClient('scripts', async client => {
|
||||||
assert.equal(
|
const [, reply] = await Promise.all([
|
||||||
await client.square(2),
|
client.set('key', '2'),
|
||||||
4
|
client.square('key')
|
||||||
);
|
]);
|
||||||
|
|
||||||
|
assert.equal(reply, 4);
|
||||||
}, {
|
}, {
|
||||||
...GLOBAL.SERVERS.OPEN,
|
...GLOBAL.SERVERS.OPEN,
|
||||||
clientOptions: {
|
clientOptions: {
|
||||||
@@ -319,12 +326,13 @@ describe('Client', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
testUtils.testWithClient('functions', async client => {
|
testUtils.testWithClient('functions', async client => {
|
||||||
await loadMathFunction(client);
|
const [,, reply] = await Promise.all([
|
||||||
|
loadMathFunction(client),
|
||||||
|
client.set('key', '2'),
|
||||||
|
client.math.square('key')
|
||||||
|
]);
|
||||||
|
|
||||||
assert.equal(
|
assert.equal(reply, 4);
|
||||||
await client.math.square(2),
|
|
||||||
4
|
|
||||||
);
|
|
||||||
}, {
|
}, {
|
||||||
...GLOBAL.SERVERS.OPEN,
|
...GLOBAL.SERVERS.OPEN,
|
||||||
minimumDockerVersion: [7, 0],
|
minimumDockerVersion: [7, 0],
|
||||||
|
@@ -1,72 +1,73 @@
|
|||||||
// import { strict as assert } from 'node:assert';
|
import { strict as assert } from 'node:assert';
|
||||||
// import testUtils, { GLOBAL, waitTillBeenCalled } from '../test-utils';
|
import testUtils, { GLOBAL, waitTillBeenCalled } from '../test-utils';
|
||||||
// import RedisCluster from '.';
|
import RedisCluster from '.';
|
||||||
// import { ClusterSlotStates } from '../commands/CLUSTER_SETSLOT';
|
// import { ClusterSlotStates } from '../commands/CLUSTER_SETSLOT';
|
||||||
// import { commandOptions } from '../command-options';
|
import { SQUARE_SCRIPT } from '../client/index.spec';
|
||||||
// import { SQUARE_SCRIPT } from '../client/index.spec';
|
import { RootNodesUnavailableError } from '../errors';
|
||||||
// import { RootNodesUnavailableError } from '../errors';
|
import { spy } from 'sinon';
|
||||||
// import { spy } from 'sinon';
|
// import { setTimeout } from 'node:timers/promises';
|
||||||
// import { setTimeout } from 'timers/promises';
|
import RedisClient from '../client';
|
||||||
// import RedisClient from '../client';
|
|
||||||
|
|
||||||
// describe('Cluster', () => {
|
describe('Cluster', () => {
|
||||||
// testUtils.testWithCluster('sendCommand', async cluster => {
|
testUtils.testWithCluster('sendCommand', async cluster => {
|
||||||
// assert.equal(
|
assert.equal(
|
||||||
// await cluster.sendCommand(undefined, true, ['PING']),
|
await cluster.sendCommand(undefined, true, ['PING']),
|
||||||
// 'PONG'
|
'PONG'
|
||||||
// );
|
);
|
||||||
// }, GLOBAL.CLUSTERS.OPEN);
|
}, GLOBAL.CLUSTERS.OPEN);
|
||||||
|
|
||||||
// testUtils.testWithCluster('isOpen', async cluster => {
|
testUtils.testWithCluster('isOpen', async cluster => {
|
||||||
// assert.equal(cluster.isOpen, true);
|
assert.equal(cluster.isOpen, true);
|
||||||
// await cluster.disconnect();
|
await cluster.destroy();
|
||||||
// assert.equal(cluster.isOpen, false);
|
assert.equal(cluster.isOpen, false);
|
||||||
// }, GLOBAL.CLUSTERS.OPEN);
|
}, GLOBAL.CLUSTERS.OPEN);
|
||||||
|
|
||||||
// testUtils.testWithCluster('connect should throw if already connected', async cluster => {
|
testUtils.testWithCluster('connect should throw if already connected', async cluster => {
|
||||||
// await assert.rejects(cluster.connect());
|
await assert.rejects(cluster.connect());
|
||||||
// }, GLOBAL.CLUSTERS.OPEN);
|
}, GLOBAL.CLUSTERS.OPEN);
|
||||||
|
|
||||||
// testUtils.testWithCluster('multi', async cluster => {
|
testUtils.testWithCluster('multi', async cluster => {
|
||||||
// const key = 'key';
|
const key = 'key';
|
||||||
// assert.deepEqual(
|
assert.deepEqual(
|
||||||
// await cluster.multi()
|
await cluster.multi()
|
||||||
// .set(key, 'value')
|
.set(key, 'value')
|
||||||
// .get(key)
|
.get(key)
|
||||||
// .exec(),
|
.exec(),
|
||||||
// ['OK', 'value']
|
['OK', 'value']
|
||||||
// );
|
);
|
||||||
// }, GLOBAL.CLUSTERS.OPEN);
|
}, GLOBAL.CLUSTERS.OPEN);
|
||||||
|
|
||||||
// testUtils.testWithCluster('scripts', async cluster => {
|
testUtils.testWithCluster('scripts', async cluster => {
|
||||||
// assert.equal(
|
const [, reply] = await Promise.all([
|
||||||
// await cluster.square(2),
|
cluster.set('key', '2'),
|
||||||
// 4
|
cluster.square('key')
|
||||||
// );
|
]);
|
||||||
// }, {
|
|
||||||
// ...GLOBAL.CLUSTERS.OPEN,
|
|
||||||
// clusterConfiguration: {
|
|
||||||
// scripts: {
|
|
||||||
// square: SQUARE_SCRIPT
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
// it('should throw RootNodesUnavailableError', async () => {
|
assert.equal(reply, 4);
|
||||||
// const cluster = RedisCluster.create({
|
}, {
|
||||||
// rootNodes: []
|
...GLOBAL.CLUSTERS.OPEN,
|
||||||
// });
|
clusterConfiguration: {
|
||||||
|
scripts: {
|
||||||
|
square: SQUARE_SCRIPT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// try {
|
it('should throw RootNodesUnavailableError', async () => {
|
||||||
// await assert.rejects(
|
const cluster = RedisCluster.create({
|
||||||
// cluster.connect(),
|
rootNodes: []
|
||||||
// RootNodesUnavailableError
|
});
|
||||||
// );
|
|
||||||
// } catch (err) {
|
try {
|
||||||
// await cluster.disconnect();
|
await assert.rejects(
|
||||||
// throw err;
|
cluster.connect(),
|
||||||
// }
|
RootNodesUnavailableError
|
||||||
// });
|
);
|
||||||
|
} catch (err) {
|
||||||
|
await cluster.disconnect();
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// testUtils.testWithCluster('should handle live resharding', async cluster => {
|
// testUtils.testWithCluster('should handle live resharding', async cluster => {
|
||||||
// const slot = 12539,
|
// const slot = 12539,
|
||||||
@@ -121,137 +122,122 @@
|
|||||||
// numberOfMasters: 2
|
// numberOfMasters: 2
|
||||||
// });
|
// });
|
||||||
|
|
||||||
// testUtils.testWithCluster('getRandomNode should spread the the load evenly', async cluster => {
|
testUtils.testWithCluster('getRandomNode should spread the the load evenly', async cluster => {
|
||||||
// const totalNodes = cluster.masters.length + cluster.replicas.length,
|
const totalNodes = cluster.masters.length + cluster.replicas.length,
|
||||||
// ids = new Set<string>();
|
ids = new Set<string>();
|
||||||
// for (let i = 0; i < totalNodes; i++) {
|
for (let i = 0; i < totalNodes; i++) {
|
||||||
// ids.add(cluster.getRandomNode().id);
|
ids.add(cluster.getRandomNode().id);
|
||||||
// }
|
}
|
||||||
|
|
||||||
// assert.equal(ids.size, totalNodes);
|
assert.equal(ids.size, totalNodes);
|
||||||
// }, GLOBAL.CLUSTERS.WITH_REPLICAS);
|
}, GLOBAL.CLUSTERS.WITH_REPLICAS);
|
||||||
|
|
||||||
// testUtils.testWithCluster('getSlotRandomNode should spread the the load evenly', async cluster => {
|
testUtils.testWithCluster('getSlotRandomNode should spread the the load evenly', async cluster => {
|
||||||
// const totalNodes = 1 + cluster.slots[0].replicas!.length,
|
const totalNodes = 1 + cluster.slots[0].replicas!.length,
|
||||||
// ids = new Set<string>();
|
ids = new Set<string>();
|
||||||
// for (let i = 0; i < totalNodes; i++) {
|
for (let i = 0; i < totalNodes; i++) {
|
||||||
// ids.add(cluster.getSlotRandomNode(0).id);
|
ids.add(cluster.getSlotRandomNode(0).id);
|
||||||
// }
|
}
|
||||||
|
|
||||||
// assert.equal(ids.size, totalNodes);
|
assert.equal(ids.size, totalNodes);
|
||||||
// }, GLOBAL.CLUSTERS.WITH_REPLICAS);
|
}, GLOBAL.CLUSTERS.WITH_REPLICAS);
|
||||||
|
|
||||||
// testUtils.testWithCluster('cluster topology', async cluster => {
|
testUtils.testWithCluster('cluster topology', async cluster => {
|
||||||
// assert.equal(cluster.slots.length, 16384);
|
assert.equal(cluster.slots.length, 16384);
|
||||||
// const { numberOfMasters, numberOfReplicas } = GLOBAL.CLUSTERS.WITH_REPLICAS;
|
const { numberOfMasters, numberOfReplicas } = GLOBAL.CLUSTERS.WITH_REPLICAS;
|
||||||
// assert.equal(cluster.shards.length, numberOfMasters);
|
assert.equal(cluster.shards.length, numberOfMasters);
|
||||||
// assert.equal(cluster.masters.length, numberOfMasters);
|
assert.equal(cluster.masters.length, numberOfMasters);
|
||||||
// assert.equal(cluster.replicas.length, numberOfReplicas * numberOfMasters);
|
assert.equal(cluster.replicas.length, numberOfReplicas * numberOfMasters);
|
||||||
// assert.equal(cluster.nodeByAddress.size, numberOfMasters + numberOfMasters * numberOfReplicas);
|
assert.equal(cluster.nodeByAddress.size, numberOfMasters + numberOfMasters * numberOfReplicas);
|
||||||
// }, GLOBAL.CLUSTERS.WITH_REPLICAS);
|
}, GLOBAL.CLUSTERS.WITH_REPLICAS);
|
||||||
|
|
||||||
// testUtils.testWithCluster('getMasters should be backwards competiable (without `minimizeConnections`)', async cluster => {
|
testUtils.testWithCluster('getMasters should be backwards competiable (without `minimizeConnections`)', async cluster => {
|
||||||
// const masters = cluster.getMasters();
|
const masters = cluster.getMasters();
|
||||||
// assert.ok(Array.isArray(masters));
|
assert.ok(Array.isArray(masters));
|
||||||
// for (const master of masters) {
|
for (const master of masters) {
|
||||||
// assert.equal(typeof master.id, 'string');
|
assert.equal(typeof master.id, 'string');
|
||||||
// assert.ok(master.client instanceof RedisClient);
|
assert.ok(master.client instanceof RedisClient);
|
||||||
// }
|
}
|
||||||
// }, {
|
}, {
|
||||||
// ...GLOBAL.CLUSTERS.OPEN,
|
...GLOBAL.CLUSTERS.OPEN,
|
||||||
// clusterConfiguration: {
|
clusterConfiguration: {
|
||||||
// minimizeConnections: undefined // reset to default
|
minimizeConnections: undefined // reset to default
|
||||||
// }
|
}
|
||||||
// });
|
});
|
||||||
|
|
||||||
// testUtils.testWithCluster('getSlotMaster should be backwards competiable (without `minimizeConnections`)', async cluster => {
|
testUtils.testWithCluster('getSlotMaster should be backwards competiable (without `minimizeConnections`)', async cluster => {
|
||||||
// const master = cluster.getSlotMaster(0);
|
const master = cluster.getSlotMaster(0);
|
||||||
// assert.equal(typeof master.id, 'string');
|
assert.equal(typeof master.id, 'string');
|
||||||
// assert.ok(master.client instanceof RedisClient);
|
assert.ok(master.client instanceof RedisClient);
|
||||||
// }, {
|
}, {
|
||||||
// ...GLOBAL.CLUSTERS.OPEN,
|
...GLOBAL.CLUSTERS.OPEN,
|
||||||
// clusterConfiguration: {
|
clusterConfiguration: {
|
||||||
// minimizeConnections: undefined // reset to default
|
minimizeConnections: undefined // reset to default
|
||||||
// }
|
}
|
||||||
// });
|
});
|
||||||
|
|
||||||
// testUtils.testWithCluster('should throw CROSSSLOT error', async cluster => {
|
testUtils.testWithCluster('should throw CROSSSLOT error', async cluster => {
|
||||||
// await assert.rejects(cluster.mGet(['a', 'b']));
|
await assert.rejects(cluster.mGet(['a', 'b']));
|
||||||
// }, GLOBAL.CLUSTERS.OPEN);
|
}, GLOBAL.CLUSTERS.OPEN);
|
||||||
|
|
||||||
// testUtils.testWithCluster('should send commands with commandOptions to correct cluster slot (without redirections)', async cluster => {
|
describe('minimizeConnections', () => {
|
||||||
// // 'a' and 'b' hash to different cluster slots (see previous unit test)
|
testUtils.testWithCluster('false', async cluster => {
|
||||||
// // -> maxCommandRedirections 0: rejects on MOVED/ASK reply
|
for (const master of cluster.masters) {
|
||||||
// await cluster.set(commandOptions({ isolated: true }), 'a', '1'),
|
assert.ok(master.client instanceof RedisClient);
|
||||||
// await cluster.set(commandOptions({ isolated: true }), 'b', '2'),
|
}
|
||||||
|
}, {
|
||||||
|
...GLOBAL.CLUSTERS.OPEN,
|
||||||
|
clusterConfiguration: {
|
||||||
|
minimizeConnections: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// assert.equal(await cluster.get('a'), '1');
|
testUtils.testWithCluster('true', async cluster => {
|
||||||
// assert.equal(await cluster.get('b'), '2');
|
for (const master of cluster.masters) {
|
||||||
// }, {
|
assert.equal(master.client, undefined);
|
||||||
// ...GLOBAL.CLUSTERS.OPEN,
|
}
|
||||||
// clusterConfiguration: {
|
}, {
|
||||||
// maxCommandRedirections: 0
|
...GLOBAL.CLUSTERS.OPEN,
|
||||||
// }
|
clusterConfiguration: {
|
||||||
// });
|
minimizeConnections: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// describe('minimizeConnections', () => {
|
describe('PubSub', () => {
|
||||||
// testUtils.testWithCluster('false', async cluster => {
|
testUtils.testWithCluster('subscribe & unsubscribe', async cluster => {
|
||||||
// for (const master of cluster.masters) {
|
const listener = spy();
|
||||||
// assert.ok(master.client instanceof RedisClient);
|
|
||||||
// }
|
|
||||||
// }, {
|
|
||||||
// ...GLOBAL.CLUSTERS.OPEN,
|
|
||||||
// clusterConfiguration: {
|
|
||||||
// minimizeConnections: false
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
// testUtils.testWithCluster('true', async cluster => {
|
await cluster.subscribe('channel', listener);
|
||||||
// for (const master of cluster.masters) {
|
|
||||||
// assert.equal(master.client, undefined);
|
|
||||||
// }
|
|
||||||
// }, {
|
|
||||||
// ...GLOBAL.CLUSTERS.OPEN,
|
|
||||||
// clusterConfiguration: {
|
|
||||||
// minimizeConnections: true
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
|
|
||||||
// describe('PubSub', () => {
|
await Promise.all([
|
||||||
// testUtils.testWithCluster('subscribe & unsubscribe', async cluster => {
|
waitTillBeenCalled(listener),
|
||||||
// const listener = spy();
|
cluster.publish('channel', 'message')
|
||||||
|
]);
|
||||||
|
|
||||||
// await cluster.subscribe('channel', listener);
|
assert.ok(listener.calledOnceWithExactly('message', 'channel'));
|
||||||
|
|
||||||
// await Promise.all([
|
await cluster.unsubscribe('channel', listener);
|
||||||
// waitTillBeenCalled(listener),
|
|
||||||
// cluster.publish('channel', 'message')
|
|
||||||
// ]);
|
|
||||||
|
|
||||||
// assert.ok(listener.calledOnceWithExactly('message', 'channel'));
|
assert.equal(cluster.pubSubNode, undefined);
|
||||||
|
}, GLOBAL.CLUSTERS.OPEN);
|
||||||
|
|
||||||
// await cluster.unsubscribe('channel', listener);
|
testUtils.testWithCluster('psubscribe & punsubscribe', async cluster => {
|
||||||
|
const listener = spy();
|
||||||
|
|
||||||
// assert.equal(cluster.pubSubNode, undefined);
|
await cluster.pSubscribe('channe*', listener);
|
||||||
// }, GLOBAL.CLUSTERS.OPEN);
|
|
||||||
|
|
||||||
// testUtils.testWithCluster('psubscribe & punsubscribe', async cluster => {
|
await Promise.all([
|
||||||
// const listener = spy();
|
waitTillBeenCalled(listener),
|
||||||
|
cluster.publish('channel', 'message')
|
||||||
|
]);
|
||||||
|
|
||||||
// await cluster.pSubscribe('channe*', listener);
|
assert.ok(listener.calledOnceWithExactly('message', 'channel'));
|
||||||
|
|
||||||
// await Promise.all([
|
await cluster.pUnsubscribe('channe*', listener);
|
||||||
// waitTillBeenCalled(listener),
|
|
||||||
// cluster.publish('channel', 'message')
|
|
||||||
// ]);
|
|
||||||
|
|
||||||
// assert.ok(listener.calledOnceWithExactly('message', 'channel'));
|
assert.equal(cluster.pubSubNode, undefined);
|
||||||
|
}, GLOBAL.CLUSTERS.OPEN);
|
||||||
// await cluster.pUnsubscribe('channe*', listener);
|
|
||||||
|
|
||||||
// assert.equal(cluster.pubSubNode, undefined);
|
|
||||||
// }, GLOBAL.CLUSTERS.OPEN);
|
|
||||||
|
|
||||||
// testUtils.testWithCluster('should move listeners when PubSub node disconnects from the cluster', async cluster => {
|
// testUtils.testWithCluster('should move listeners when PubSub node disconnects from the cluster', async cluster => {
|
||||||
// const listener = spy();
|
// const listener = spy();
|
||||||
@@ -302,26 +288,26 @@
|
|||||||
// minimumDockerVersion: [7]
|
// minimumDockerVersion: [7]
|
||||||
// });
|
// });
|
||||||
|
|
||||||
// testUtils.testWithCluster('ssubscribe & sunsubscribe', async cluster => {
|
testUtils.testWithCluster('ssubscribe & sunsubscribe', async cluster => {
|
||||||
// const listener = spy();
|
const listener = spy();
|
||||||
|
|
||||||
// await cluster.sSubscribe('channel', listener);
|
await cluster.sSubscribe('channel', listener);
|
||||||
|
|
||||||
// await Promise.all([
|
await Promise.all([
|
||||||
// waitTillBeenCalled(listener),
|
waitTillBeenCalled(listener),
|
||||||
// cluster.sPublish('channel', 'message')
|
cluster.sPublish('channel', 'message')
|
||||||
// ]);
|
]);
|
||||||
|
|
||||||
// assert.ok(listener.calledOnceWithExactly('message', 'channel'));
|
assert.ok(listener.calledOnceWithExactly('message', 'channel'));
|
||||||
|
|
||||||
// await cluster.sUnsubscribe('channel', listener);
|
await cluster.sUnsubscribe('channel', listener);
|
||||||
|
|
||||||
// // 10328 is the slot of `channel`
|
// 10328 is the slot of `channel`
|
||||||
// assert.equal(cluster.slots[10328].master.pubSubClient, undefined);
|
assert.equal(cluster.slots[10328].master.pubSubClient, undefined);
|
||||||
// }, {
|
}, {
|
||||||
// ...GLOBAL.CLUSTERS.OPEN,
|
...GLOBAL.CLUSTERS.OPEN,
|
||||||
// minimumDockerVersion: [7]
|
minimumDockerVersion: [7]
|
||||||
// });
|
});
|
||||||
|
|
||||||
// testUtils.testWithCluster('should handle sharded-channel-moved events', async cluster => {
|
// testUtils.testWithCluster('should handle sharded-channel-moved events', async cluster => {
|
||||||
// const SLOT = 10328,
|
// const SLOT = 10328,
|
||||||
@@ -358,5 +344,5 @@
|
|||||||
// serverArguments: [],
|
// serverArguments: [],
|
||||||
// minimumDockerVersion: [7]
|
// minimumDockerVersion: [7]
|
||||||
// });
|
// });
|
||||||
// });
|
});
|
||||||
// });
|
});
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { RedisClientOptions, RedisClientType } from '../client';
|
import { RedisClientOptions, RedisClientType } from '../client';
|
||||||
import { CommandOptions } from '../client/commands-queue';
|
import { CommandOptions } from '../client/commands-queue';
|
||||||
import { Command, CommandArguments, CommanderConfig, CommandPolicies, CommandWithPoliciesSignature, TypeMapping, RedisArgument, RedisFunction, RedisFunctions, RedisModules, RedisScript, RedisScripts, ReplyUnion, RespVersions } from '../RESP/types';
|
import { Command, CommandArguments, CommanderConfig, CommandSignature, /*CommandPolicies, CommandWithPoliciesSignature,*/ TypeMapping, RedisArgument, RedisFunction, RedisFunctions, RedisModules, RedisScript, RedisScripts, ReplyUnion, RespVersions } from '../RESP/types';
|
||||||
import COMMANDS from '../commands';
|
import COMMANDS from '../commands';
|
||||||
import { EventEmitter } from 'node:events';
|
import { EventEmitter } from 'node:events';
|
||||||
import { attachConfig, functionArgumentsPrefix, getTransformReply, scriptArgumentsPrefix } from '../commander';
|
import { attachConfig, functionArgumentsPrefix, getTransformReply, scriptArgumentsPrefix } from '../commander';
|
||||||
@@ -65,12 +65,48 @@ export interface RedisClusterOptions<
|
|||||||
nodeAddressMap?: NodeAddressMap;
|
nodeAddressMap?: NodeAddressMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// remove once request & response policies are ready
|
||||||
|
type ClusterCommand<
|
||||||
|
NAME extends PropertyKey,
|
||||||
|
COMMAND extends Command
|
||||||
|
> = COMMAND['FIRST_KEY_INDEX'] extends undefined ? (
|
||||||
|
COMMAND['IS_FORWARD_COMMAND'] extends true ? NAME : never
|
||||||
|
) : NAME;
|
||||||
|
|
||||||
|
// CommandWithPoliciesSignature<(typeof COMMANDS)[P], RESP, TYPE_MAPPING, POLICIES>
|
||||||
type WithCommands<
|
type WithCommands<
|
||||||
RESP extends RespVersions,
|
RESP extends RespVersions,
|
||||||
TYPE_MAPPING extends TypeMapping,
|
TYPE_MAPPING extends TypeMapping
|
||||||
// POLICIES extends CommandPolicies
|
|
||||||
> = {
|
> = {
|
||||||
[P in keyof typeof COMMANDS]: CommandWithPoliciesSignature<(typeof COMMANDS)[P], RESP, TYPE_MAPPING, POLICIES>;
|
[P in keyof typeof COMMANDS as ClusterCommand<P, (typeof COMMANDS)[P]>]: CommandSignature<(typeof COMMANDS)[P], RESP, TYPE_MAPPING>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type WithModules<
|
||||||
|
M extends RedisModules,
|
||||||
|
RESP extends RespVersions,
|
||||||
|
TYPE_MAPPING extends TypeMapping
|
||||||
|
> = {
|
||||||
|
[P in keyof M]: {
|
||||||
|
[C in keyof M[P] as ClusterCommand<C, M[P][C]>]: CommandSignature<M[P][C], RESP, TYPE_MAPPING>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type WithFunctions<
|
||||||
|
F extends RedisFunctions,
|
||||||
|
RESP extends RespVersions,
|
||||||
|
TYPE_MAPPING extends TypeMapping
|
||||||
|
> = {
|
||||||
|
[L in keyof F]: {
|
||||||
|
[C in keyof F[L] as ClusterCommand<C, F[L][C]>]: CommandSignature<F[L][C], RESP, TYPE_MAPPING>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type WithScripts<
|
||||||
|
S extends RedisScripts,
|
||||||
|
RESP extends RespVersions,
|
||||||
|
TYPE_MAPPING extends TypeMapping
|
||||||
|
> = {
|
||||||
|
[P in keyof S as ClusterCommand<P, S[P]>]: CommandSignature<S[P], RESP, TYPE_MAPPING>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RedisClusterType<
|
export type RedisClusterType<
|
||||||
@@ -80,17 +116,22 @@ export type RedisClusterType<
|
|||||||
RESP extends RespVersions = 2,
|
RESP extends RespVersions = 2,
|
||||||
TYPE_MAPPING extends TypeMapping = {},
|
TYPE_MAPPING extends TypeMapping = {},
|
||||||
// POLICIES extends CommandPolicies = {}
|
// POLICIES extends CommandPolicies = {}
|
||||||
> = RedisCluster<M, F, S, RESP, TYPE_MAPPING, POLICIES> & WithCommands<RESP, TYPE_MAPPING/*, POLICIES*/>;
|
> = (
|
||||||
// & WithModules<M> & WithFunctions<F> & WithScripts<S>
|
RedisCluster<M, F, S, RESP, TYPE_MAPPING/*, POLICIES*/> &
|
||||||
|
WithCommands<RESP, TYPE_MAPPING> &
|
||||||
|
WithModules<M, RESP, TYPE_MAPPING> &
|
||||||
|
WithFunctions<F, RESP, TYPE_MAPPING> &
|
||||||
|
WithScripts<S, RESP, TYPE_MAPPING>
|
||||||
|
);
|
||||||
|
|
||||||
export interface ClusterCommandOptions<
|
export interface ClusterCommandOptions<
|
||||||
TYPE_MAPPING extends TypeMapping = TypeMapping,
|
TYPE_MAPPING extends TypeMapping = TypeMapping
|
||||||
POLICIES extends CommandPolicies = CommandPolicies
|
// POLICIES extends CommandPolicies = CommandPolicies
|
||||||
> extends CommandOptions<TYPE_MAPPING> {
|
> extends CommandOptions<TYPE_MAPPING> {
|
||||||
policies?: POLICIES;
|
// policies?: POLICIES;
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProxyCluster = RedisCluster<any, any, any, any, any, any>;
|
type ProxyCluster = RedisCluster<any, any, any, any, any/*, any*/>;
|
||||||
|
|
||||||
type NamespaceProxyCluster = { self: ProxyCluster };
|
type NamespaceProxyCluster = { self: ProxyCluster };
|
||||||
|
|
||||||
@@ -100,20 +141,30 @@ export default class RedisCluster<
|
|||||||
S extends RedisScripts,
|
S extends RedisScripts,
|
||||||
RESP extends RespVersions,
|
RESP extends RespVersions,
|
||||||
TYPE_MAPPING extends TypeMapping,
|
TYPE_MAPPING extends TypeMapping,
|
||||||
POLICIES extends CommandPolicies
|
// POLICIES extends CommandPolicies
|
||||||
> extends EventEmitter {
|
> extends EventEmitter {
|
||||||
static extractFirstKey<C extends Command>(
|
static extractFirstKey<C extends Command>(
|
||||||
command: C,
|
command: C,
|
||||||
args: Parameters<C['transformArguments']>,
|
args: Parameters<C['transformArguments']>,
|
||||||
redisArgs: Array<RedisArgument>
|
redisArgs: Array<RedisArgument>
|
||||||
): RedisArgument | undefined {
|
) {
|
||||||
if (command.FIRST_KEY_INDEX === undefined) {
|
let key: RedisArgument | undefined;
|
||||||
return undefined;
|
switch (typeof command.FIRST_KEY_INDEX) {
|
||||||
} else if (typeof command.FIRST_KEY_INDEX === 'number') {
|
case 'number':
|
||||||
return redisArgs[command.FIRST_KEY_INDEX];
|
key = redisArgs[command.FIRST_KEY_INDEX];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'function':
|
||||||
|
key = command.FIRST_KEY_INDEX(...args);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return command.FIRST_KEY_INDEX(...args);
|
// TODO: remove once request & response policies are ready
|
||||||
|
if (key === undefined && !command.IS_FORWARD_COMMAND) {
|
||||||
|
throw new Error('TODO');
|
||||||
|
}
|
||||||
|
|
||||||
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static _createCommand(command: Command, resp: RespVersions) {
|
private static _createCommand(command: Command, resp: RespVersions) {
|
||||||
@@ -130,7 +181,7 @@ export default class RedisCluster<
|
|||||||
command.IS_READ_ONLY,
|
command.IS_READ_ONLY,
|
||||||
redisArgs,
|
redisArgs,
|
||||||
this._commandOptions,
|
this._commandOptions,
|
||||||
command.POLICIES
|
// command.POLICIES
|
||||||
);
|
);
|
||||||
|
|
||||||
return transformReply ?
|
return transformReply ?
|
||||||
@@ -153,7 +204,7 @@ export default class RedisCluster<
|
|||||||
command.IS_READ_ONLY,
|
command.IS_READ_ONLY,
|
||||||
redisArgs,
|
redisArgs,
|
||||||
this.self._commandOptions,
|
this.self._commandOptions,
|
||||||
command.POLICIES
|
// command.POLICIES
|
||||||
);
|
);
|
||||||
|
|
||||||
return transformReply ?
|
return transformReply ?
|
||||||
@@ -167,18 +218,18 @@ export default class RedisCluster<
|
|||||||
transformReply = getTransformReply(fn, resp);
|
transformReply = getTransformReply(fn, resp);
|
||||||
return async function (this: NamespaceProxyCluster, ...args: Array<unknown>) {
|
return async function (this: NamespaceProxyCluster, ...args: Array<unknown>) {
|
||||||
const fnArgs = fn.transformArguments(...args),
|
const fnArgs = fn.transformArguments(...args),
|
||||||
redisArgs = prefix.concat(fnArgs),
|
|
||||||
firstKey = RedisCluster.extractFirstKey(
|
firstKey = RedisCluster.extractFirstKey(
|
||||||
fn,
|
fn,
|
||||||
fnArgs,
|
args,
|
||||||
redisArgs
|
fnArgs
|
||||||
),
|
),
|
||||||
|
redisArgs = prefix.concat(fnArgs),
|
||||||
reply = await this.self.sendCommand(
|
reply = await this.self.sendCommand(
|
||||||
firstKey,
|
firstKey,
|
||||||
fn.IS_READ_ONLY,
|
fn.IS_READ_ONLY,
|
||||||
redisArgs,
|
redisArgs,
|
||||||
this.self._commandOptions,
|
this.self._commandOptions,
|
||||||
fn.POLICIES
|
// fn.POLICIES
|
||||||
);
|
);
|
||||||
|
|
||||||
return transformReply ?
|
return transformReply ?
|
||||||
@@ -192,18 +243,19 @@ export default class RedisCluster<
|
|||||||
transformReply = getTransformReply(script, resp);
|
transformReply = getTransformReply(script, resp);
|
||||||
return async function (this: ProxyCluster, ...args: Array<unknown>) {
|
return async function (this: ProxyCluster, ...args: Array<unknown>) {
|
||||||
const scriptArgs = script.transformArguments(...args),
|
const scriptArgs = script.transformArguments(...args),
|
||||||
redisArgs = prefix.concat(scriptArgs),
|
|
||||||
firstKey = RedisCluster.extractFirstKey(
|
firstKey = RedisCluster.extractFirstKey(
|
||||||
script,
|
script,
|
||||||
scriptArgs,
|
args,
|
||||||
redisArgs
|
scriptArgs
|
||||||
),
|
),
|
||||||
reply = await this.sendCommand(
|
redisArgs = prefix.concat(scriptArgs),
|
||||||
|
reply = await this.executeScript(
|
||||||
|
script,
|
||||||
firstKey,
|
firstKey,
|
||||||
script.IS_READ_ONLY,
|
script.IS_READ_ONLY,
|
||||||
redisArgs,
|
redisArgs,
|
||||||
this._commandOptions,
|
this._commandOptions,
|
||||||
script.POLICIES
|
// script.POLICIES
|
||||||
);
|
);
|
||||||
|
|
||||||
return transformReply ?
|
return transformReply ?
|
||||||
@@ -218,8 +270,8 @@ export default class RedisCluster<
|
|||||||
S extends RedisScripts = {},
|
S extends RedisScripts = {},
|
||||||
RESP extends RespVersions = 2,
|
RESP extends RespVersions = 2,
|
||||||
TYPE_MAPPING extends TypeMapping = {},
|
TYPE_MAPPING extends TypeMapping = {},
|
||||||
POLICIES extends CommandPolicies = {}
|
// POLICIES extends CommandPolicies = {}
|
||||||
>(config?: ClusterCommander<M, F, S, RESP, TYPE_MAPPING, POLICIES>) {
|
>(config?: ClusterCommander<M, F, S, RESP, TYPE_MAPPING/*, POLICIES*/>) {
|
||||||
const Cluster = attachConfig({
|
const Cluster = attachConfig({
|
||||||
BaseClass: RedisCluster,
|
BaseClass: RedisCluster,
|
||||||
commands: COMMANDS,
|
commands: COMMANDS,
|
||||||
@@ -234,7 +286,7 @@ export default class RedisCluster<
|
|||||||
|
|
||||||
return (options?: Omit<RedisClusterOptions, keyof Exclude<typeof config, undefined>>) => {
|
return (options?: Omit<RedisClusterOptions, keyof Exclude<typeof config, undefined>>) => {
|
||||||
// returning a "proxy" to prevent the namespaces.self to leak between "proxies"
|
// returning a "proxy" to prevent the namespaces.self to leak between "proxies"
|
||||||
return Object.create(new Cluster(options)) as RedisClusterType<M, F, S, RESP, TYPE_MAPPING, POLICIES>;
|
return Object.create(new Cluster(options)) as RedisClusterType<M, F, S, RESP, TYPE_MAPPING/*, POLICIES*/>;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,16 +296,16 @@ export default class RedisCluster<
|
|||||||
S extends RedisScripts = {},
|
S extends RedisScripts = {},
|
||||||
RESP extends RespVersions = 2,
|
RESP extends RespVersions = 2,
|
||||||
TYPE_MAPPING extends TypeMapping = {},
|
TYPE_MAPPING extends TypeMapping = {},
|
||||||
POLICIES extends CommandPolicies = {}
|
// POLICIES extends CommandPolicies = {}
|
||||||
>(options?: RedisClusterOptions<M, F, S, RESP, TYPE_MAPPING, POLICIES>) {
|
>(options?: RedisClusterOptions<M, F, S, RESP, TYPE_MAPPING/*, POLICIES*/>) {
|
||||||
return RedisCluster.factory(options)(options);
|
return RedisCluster.factory(options)(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly _options: RedisClusterOptions<M, F, S, RESP, TYPE_MAPPING, POLICIES>;
|
private readonly _options: RedisClusterOptions<M, F, S, RESP, TYPE_MAPPING/*, POLICIES*/>;
|
||||||
|
|
||||||
private readonly _slots: RedisClusterSlots<M, F, S, RESP>;
|
private readonly _slots: RedisClusterSlots<M, F, S, RESP>;
|
||||||
|
|
||||||
private _commandOptions?: ClusterCommandOptions<TYPE_MAPPING, POLICIES>;
|
private _commandOptions?: ClusterCommandOptions<TYPE_MAPPING/*, POLICIES*/>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An array of the cluster slots, each slot contain its `master` and `replicas`.
|
* An array of the cluster slots, each slot contain its `master` and `replicas`.
|
||||||
@@ -306,7 +358,7 @@ export default class RedisCluster<
|
|||||||
return this._slots.isOpen;
|
return this._slots.isOpen;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(options: RedisClusterOptions<M, F, S, RESP, TYPE_MAPPING, POLICIES>) {
|
constructor(options: RedisClusterOptions<M, F, S, RESP, TYPE_MAPPING/*, POLICIES*/>) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this._options = options;
|
this._options = options;
|
||||||
@@ -336,9 +388,9 @@ export default class RedisCluster<
|
|||||||
}
|
}
|
||||||
|
|
||||||
withCommandOptions<
|
withCommandOptions<
|
||||||
OPTIONS extends ClusterCommandOptions<TYPE_MAPPING, CommandPolicies>,
|
OPTIONS extends ClusterCommandOptions<TYPE_MAPPING/*, CommandPolicies*/>,
|
||||||
TYPE_MAPPING extends TypeMapping,
|
TYPE_MAPPING extends TypeMapping,
|
||||||
POLICIES extends CommandPolicies
|
// POLICIES extends CommandPolicies
|
||||||
>(options: OPTIONS) {
|
>(options: OPTIONS) {
|
||||||
const proxy = Object.create(this);
|
const proxy = Object.create(this);
|
||||||
proxy._commandOptions = options;
|
proxy._commandOptions = options;
|
||||||
@@ -347,8 +399,8 @@ export default class RedisCluster<
|
|||||||
F,
|
F,
|
||||||
S,
|
S,
|
||||||
RESP,
|
RESP,
|
||||||
TYPE_MAPPING extends TypeMapping ? TYPE_MAPPING : {},
|
TYPE_MAPPING extends TypeMapping ? TYPE_MAPPING : {}
|
||||||
POLICIES extends CommandPolicies ? POLICIES : {}
|
// POLICIES extends CommandPolicies ? POLICIES : {}
|
||||||
>;
|
>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,8 +419,8 @@ export default class RedisCluster<
|
|||||||
F,
|
F,
|
||||||
S,
|
S,
|
||||||
RESP,
|
RESP,
|
||||||
K extends 'typeMapping' ? V extends TypeMapping ? V : {} : TYPE_MAPPING,
|
K extends 'typeMapping' ? V extends TypeMapping ? V : {} : TYPE_MAPPING
|
||||||
K extends 'policies' ? V extends CommandPolicies ? V : {} : POLICIES
|
// K extends 'policies' ? V extends CommandPolicies ? V : {} : POLICIES
|
||||||
>;
|
>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -379,15 +431,15 @@ export default class RedisCluster<
|
|||||||
return this._commandOptionsProxy('typeMapping', typeMapping);
|
return this._commandOptionsProxy('typeMapping', typeMapping);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* Override the `policies` command option
|
// * Override the `policies` command option
|
||||||
* TODO
|
// * TODO
|
||||||
*/
|
// */
|
||||||
withPolicies<POLICIES extends CommandPolicies> (policies: POLICIES) {
|
// withPolicies<POLICIES extends CommandPolicies> (policies: POLICIES) {
|
||||||
return this._commandOptionsProxy('policies', policies);
|
// return this._commandOptionsProxy('policies', policies);
|
||||||
}
|
// }
|
||||||
|
|
||||||
async #execute<T>(
|
private async _execute<T>(
|
||||||
firstKey: RedisArgument | undefined,
|
firstKey: RedisArgument | undefined,
|
||||||
isReadonly: boolean | undefined,
|
isReadonly: boolean | undefined,
|
||||||
fn: (client: RedisClientType<M, F, S, RESP>) => Promise<T>
|
fn: (client: RedisClientType<M, F, S, RESP>) => Promise<T>
|
||||||
@@ -437,9 +489,9 @@ export default class RedisCluster<
|
|||||||
isReadonly: boolean | undefined,
|
isReadonly: boolean | undefined,
|
||||||
args: CommandArguments,
|
args: CommandArguments,
|
||||||
options?: ClusterCommandOptions,
|
options?: ClusterCommandOptions,
|
||||||
defaultPolicies?: CommandPolicies
|
// defaultPolicies?: CommandPolicies
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
return this.#execute(
|
return this._execute(
|
||||||
firstKey,
|
firstKey,
|
||||||
isReadonly,
|
isReadonly,
|
||||||
client => client.sendCommand(args, options)
|
client => client.sendCommand(args, options)
|
||||||
@@ -453,7 +505,7 @@ export default class RedisCluster<
|
|||||||
args: Array<RedisArgument>,
|
args: Array<RedisArgument>,
|
||||||
options?: CommandOptions
|
options?: CommandOptions
|
||||||
) {
|
) {
|
||||||
return this.#execute(
|
return this._execute(
|
||||||
firstKey,
|
firstKey,
|
||||||
isReadonly,
|
isReadonly,
|
||||||
client => client.executeScript(script, args, options)
|
client => client.executeScript(script, args, options)
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { VerbatimStringReply, Command } from '@redis/client/dist/lib/RESP/types';
|
import { VerbatimStringReply, Command } from '../RESP/types';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
FIRST_KEY_INDEX: undefined,
|
FIRST_KEY_INDEX: undefined,
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { VerbatimStringReply, Command } from '@redis/client/dist/lib/RESP/types';
|
import { VerbatimStringReply, Command } from '../RESP/types';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
FIRST_KEY_INDEX: undefined,
|
FIRST_KEY_INDEX: undefined,
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { RedisArgument, VerbatimStringReply, Command } from '@redis/client/dist/lib/RESP/types';
|
import { RedisArgument, VerbatimStringReply, Command } from '../RESP/types';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
FIRST_KEY_INDEX: undefined,
|
FIRST_KEY_INDEX: undefined,
|
||||||
|
@@ -17,13 +17,14 @@ describe('FCALL', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
testUtils.testWithClient('client.fCall', async client => {
|
testUtils.testWithClient('client.fCall', async client => {
|
||||||
await loadMathFunction(client);
|
const [,, reply] = await Promise.all([
|
||||||
|
loadMathFunction(client),
|
||||||
|
client.set('key', '2'),
|
||||||
|
client.fCall(MATH_FUNCTION.library.square.NAME, {
|
||||||
|
arguments: ['key']
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
assert.equal(
|
assert.equal(reply, 4);
|
||||||
await client.fCall(MATH_FUNCTION.library.square.NAME, {
|
|
||||||
arguments: ['2']
|
|
||||||
}),
|
|
||||||
4
|
|
||||||
);
|
|
||||||
}, GLOBAL.SERVERS.OPEN);
|
}, GLOBAL.SERVERS.OPEN);
|
||||||
});
|
});
|
||||||
|
@@ -17,13 +17,14 @@ describe('FCALL_RO', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
testUtils.testWithClient('client.fCallRo', async client => {
|
testUtils.testWithClient('client.fCallRo', async client => {
|
||||||
await loadMathFunction(client);
|
const [,, reply] = await Promise.all([
|
||||||
|
loadMathFunction(client),
|
||||||
|
client.set('key', '2'),
|
||||||
|
client.fCallRo(MATH_FUNCTION.library.square.NAME, {
|
||||||
|
arguments: ['key']
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
assert.equal(
|
assert.equal(reply, 4);
|
||||||
await client.fCallRo(MATH_FUNCTION.library.square.NAME, {
|
|
||||||
arguments: ['2']
|
|
||||||
}),
|
|
||||||
4
|
|
||||||
);
|
|
||||||
}, GLOBAL.SERVERS.OPEN);
|
}, GLOBAL.SERVERS.OPEN);
|
||||||
});
|
});
|
||||||
|
@@ -11,16 +11,20 @@ export const MATH_FUNCTION = {
|
|||||||
`#!LUA name=math
|
`#!LUA name=math
|
||||||
redis.register_function {
|
redis.register_function {
|
||||||
function_name = "square",
|
function_name = "square",
|
||||||
callback = function(keys, args) return args[1] * args[1] end,
|
callback = function(keys, args) {
|
||||||
|
local number = redis.call('GET', keys[1])
|
||||||
|
return number * number
|
||||||
|
},
|
||||||
flags = { "no-writes" }
|
flags = { "no-writes" }
|
||||||
}`,
|
}`,
|
||||||
library: {
|
library: {
|
||||||
square: {
|
square: {
|
||||||
NAME: 'square',
|
NAME: 'square',
|
||||||
IS_READ_ONLY: true,
|
IS_READ_ONLY: true,
|
||||||
NUMBER_OF_KEYS: 0,
|
NUMBER_OF_KEYS: 1,
|
||||||
transformArguments(number: number) {
|
FIRST_KEY_INDEX: 0,
|
||||||
return [number.toString()];
|
transformArguments(key: string) {
|
||||||
|
return [key];
|
||||||
},
|
},
|
||||||
transformReply: undefined as unknown as () => NumberReply
|
transformReply: undefined as unknown as () => NumberReply
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,7 @@ import { RedisArgument, NumberReply, Command } from '../RESP/types';
|
|||||||
export default {
|
export default {
|
||||||
FIRST_KEY_INDEX: undefined,
|
FIRST_KEY_INDEX: undefined,
|
||||||
IS_READ_ONLY: true,
|
IS_READ_ONLY: true,
|
||||||
|
IS_FORWARD_COMMAND: true,
|
||||||
transformArguments(channel: RedisArgument, message: RedisArgument) {
|
transformArguments(channel: RedisArgument, message: RedisArgument) {
|
||||||
return ['PUBLISH', channel, message];
|
return ['PUBLISH', channel, message];
|
||||||
},
|
},
|
||||||
|
@@ -4,14 +4,13 @@ import {
|
|||||||
RedisScripts,
|
RedisScripts,
|
||||||
RespVersions,
|
RespVersions,
|
||||||
TypeMapping,
|
TypeMapping,
|
||||||
CommandPolicies,
|
// CommandPolicies,
|
||||||
createClient,
|
createClient,
|
||||||
RedisClientOptions,
|
RedisClientOptions,
|
||||||
RedisClientType,
|
RedisClientType,
|
||||||
createCluster,
|
createCluster,
|
||||||
RedisClusterOptions,
|
RedisClusterOptions,
|
||||||
RedisClusterType,
|
RedisClusterType
|
||||||
RESP_TYPES
|
|
||||||
} from '@redis/client/index';
|
} from '@redis/client/index';
|
||||||
import { RedisServerDockerConfig, spawnRedisServer, spawnRedisCluster } from './dockers';
|
import { RedisServerDockerConfig, spawnRedisServer, spawnRedisCluster } from './dockers';
|
||||||
import yargs from 'yargs';
|
import yargs from 'yargs';
|
||||||
@@ -44,11 +43,11 @@ interface ClusterTestOptions<
|
|||||||
F extends RedisFunctions,
|
F extends RedisFunctions,
|
||||||
S extends RedisScripts,
|
S extends RedisScripts,
|
||||||
RESP extends RespVersions,
|
RESP extends RespVersions,
|
||||||
TYPE_MAPPING extends TypeMapping,
|
TYPE_MAPPING extends TypeMapping
|
||||||
POLICIES extends CommandPolicies
|
// POLICIES extends CommandPolicies
|
||||||
> extends CommonTestOptions {
|
> extends CommonTestOptions {
|
||||||
serverArguments: Array<string>;
|
serverArguments: Array<string>;
|
||||||
clusterConfiguration?: Partial<RedisClusterOptions<M, F, S, RESP, TYPE_MAPPING, POLICIES>>;
|
clusterConfiguration?: Partial<RedisClusterOptions<M, F, S, RESP, TYPE_MAPPING/*, POLICIES*/>>;
|
||||||
numberOfMasters?: number;
|
numberOfMasters?: number;
|
||||||
numberOfReplicas?: number;
|
numberOfReplicas?: number;
|
||||||
}
|
}
|
||||||
@@ -58,11 +57,11 @@ interface AllTestOptions<
|
|||||||
F extends RedisFunctions,
|
F extends RedisFunctions,
|
||||||
S extends RedisScripts,
|
S extends RedisScripts,
|
||||||
RESP extends RespVersions,
|
RESP extends RespVersions,
|
||||||
TYPE_MAPPING extends TypeMapping,
|
TYPE_MAPPING extends TypeMapping
|
||||||
POLICIES extends CommandPolicies
|
// POLICIES extends CommandPolicies
|
||||||
> {
|
> {
|
||||||
client: ClientTestOptions<M, F, S, RESP, TYPE_MAPPING>;
|
client: ClientTestOptions<M, F, S, RESP, TYPE_MAPPING>;
|
||||||
cluster: ClusterTestOptions<M, F, S, RESP, TYPE_MAPPING, POLICIES>;
|
cluster: ClusterTestOptions<M, F, S, RESP, TYPE_MAPPING/*, POLICIES*/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Version {
|
interface Version {
|
||||||
@@ -197,9 +196,9 @@ export default class TestUtils {
|
|||||||
F extends RedisFunctions,
|
F extends RedisFunctions,
|
||||||
S extends RedisScripts,
|
S extends RedisScripts,
|
||||||
RESP extends RespVersions,
|
RESP extends RespVersions,
|
||||||
TYPE_MAPPING extends TypeMapping,
|
TYPE_MAPPING extends TypeMapping
|
||||||
POLICIES extends CommandPolicies
|
// POLICIES extends CommandPolicies
|
||||||
>(cluster: RedisClusterType<M, F, S, RESP, TYPE_MAPPING, POLICIES>): Promise<unknown> {
|
>(cluster: RedisClusterType<M, F, S, RESP, TYPE_MAPPING/*, POLICIES*/>): Promise<unknown> {
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
cluster.masters.map(async ({ client }) => {
|
cluster.masters.map(async ({ client }) => {
|
||||||
if (client) {
|
if (client) {
|
||||||
@@ -214,12 +213,12 @@ export default class TestUtils {
|
|||||||
F extends RedisFunctions = {},
|
F extends RedisFunctions = {},
|
||||||
S extends RedisScripts = {},
|
S extends RedisScripts = {},
|
||||||
RESP extends RespVersions = 2,
|
RESP extends RespVersions = 2,
|
||||||
TYPE_MAPPING extends TypeMapping = {},
|
TYPE_MAPPING extends TypeMapping = {}
|
||||||
POLICIES extends CommandPolicies = {}
|
// POLICIES extends CommandPolicies = {}
|
||||||
>(
|
>(
|
||||||
title: string,
|
title: string,
|
||||||
fn: (cluster: RedisClusterType<M, F, S, RESP, TYPE_MAPPING, POLICIES>) => unknown,
|
fn: (cluster: RedisClusterType<M, F, S, RESP, TYPE_MAPPING/*, POLICIES*/>) => unknown,
|
||||||
options: ClusterTestOptions<M, F, S, RESP, TYPE_MAPPING, POLICIES>
|
options: ClusterTestOptions<M, F, S, RESP, TYPE_MAPPING/*, POLICIES*/>
|
||||||
): void {
|
): void {
|
||||||
let dockersPromise: ReturnType<typeof spawnRedisCluster>;
|
let dockersPromise: ReturnType<typeof spawnRedisCluster>;
|
||||||
if (this.isVersionGreaterThan(options.minimumDockerVersion)) {
|
if (this.isVersionGreaterThan(options.minimumDockerVersion)) {
|
||||||
@@ -267,12 +266,12 @@ export default class TestUtils {
|
|||||||
F extends RedisFunctions = {},
|
F extends RedisFunctions = {},
|
||||||
S extends RedisScripts = {},
|
S extends RedisScripts = {},
|
||||||
RESP extends RespVersions = 2,
|
RESP extends RespVersions = 2,
|
||||||
TYPE_MAPPING extends TypeMapping = {},
|
TYPE_MAPPING extends TypeMapping = {}
|
||||||
POLICIES extends CommandPolicies = {}
|
// POLICIES extends CommandPolicies = {}
|
||||||
>(
|
>(
|
||||||
title: string,
|
title: string,
|
||||||
fn: (client: RedisClientType<M, F, S, RESP, TYPE_MAPPING> | RedisClusterType<M, F, S, RESP, TYPE_MAPPING, POLICIES>) => unknown,
|
fn: (client: RedisClientType<M, F, S, RESP, TYPE_MAPPING> | RedisClusterType<M, F, S, RESP, TYPE_MAPPING/*, POLICIES*/>) => unknown,
|
||||||
options: AllTestOptions<M, F, S, RESP, TYPE_MAPPING, POLICIES>
|
options: AllTestOptions<M, F, S, RESP, TYPE_MAPPING/*, POLICIES*/>
|
||||||
) {
|
) {
|
||||||
this.testWithClient(`client.${title}`, fn, options.client);
|
this.testWithClient(`client.${title}`, fn, options.client);
|
||||||
this.testWithCluster(`cluster.${title}`, fn, options.cluster);
|
this.testWithCluster(`cluster.${title}`, fn, options.cluster);
|
||||||
|
Reference in New Issue
Block a user