You've already forked node-redis
mirror of
https://github.com/redis/node-redis.git
synced 2025-08-10 11:43:01 +03:00
implement some GEO commands, improve scan generic transformer, expose RPUSHX
This commit is contained in:
@@ -111,7 +111,7 @@ export default class RedisClient<M extends RedisModules = RedisModules, S extend
|
|||||||
promises.push(resubscribePromise);
|
promises.push(resubscribePromise);
|
||||||
this.#tick();
|
this.#tick();
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -179,12 +179,20 @@ export default class RedisClient<M extends RedisModules = RedisModules, S extend
|
|||||||
|
|
||||||
for (const [name, script] of Object.entries(this.#options.scripts)) {
|
for (const [name, script] of Object.entries(this.#options.scripts)) {
|
||||||
(this as any)[name] = async function (...args: Parameters<typeof script.transformArguments>): Promise<ReturnType<typeof script.transformReply>> {
|
(this as any)[name] = async function (...args: Parameters<typeof script.transformArguments>): Promise<ReturnType<typeof script.transformReply>> {
|
||||||
const options = isCommandOptions(args[0]) && args[0];
|
let options;
|
||||||
|
if (isCommandOptions<ClientCommandOptions>(args[0])) {
|
||||||
|
options = args[0];
|
||||||
|
args = args.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const transformedArguments = script.transformArguments(...args);
|
||||||
return script.transformReply(
|
return script.transformReply(
|
||||||
await this.executeScript(
|
await this.executeScript(
|
||||||
script,
|
script,
|
||||||
...script.transformArguments(...(options ? args.slice(1) : args))
|
transformedArguments,
|
||||||
)
|
options
|
||||||
|
),
|
||||||
|
transformedArguments.preserve
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -197,7 +205,7 @@ export default class RedisClient<M extends RedisModules = RedisModules, S extend
|
|||||||
script.SHA,
|
script.SHA,
|
||||||
script.NUMBER_OF_KEYS.toString(),
|
script.NUMBER_OF_KEYS.toString(),
|
||||||
...args
|
...args
|
||||||
], options);
|
], options);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
if (!err?.message?.startsWith?.('NOSCRIPT')) {
|
if (!err?.message?.startsWith?.('NOSCRIPT')) {
|
||||||
throw err;
|
throw err;
|
||||||
@@ -274,7 +282,7 @@ export default class RedisClient<M extends RedisModules = RedisModules, S extend
|
|||||||
const handler = (...args: Array<unknown>): void => {
|
const handler = (...args: Array<unknown>): void => {
|
||||||
(this as any).sendCommand(name, ...args);
|
(this as any).sendCommand(name, ...args);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (moduleName) {
|
if (moduleName) {
|
||||||
(this as any).#v4[moduleName][name] = (this as any)[moduleName][name];
|
(this as any).#v4[moduleName][name] = (this as any)[moduleName][name];
|
||||||
(this as any)[moduleName][name] = handler;
|
(this as any)[moduleName][name] = handler;
|
||||||
@@ -372,12 +380,14 @@ export default class RedisClient<M extends RedisModules = RedisModules, S extend
|
|||||||
options = args[0];
|
options = args[0];
|
||||||
args = args.slice(1);
|
args = args.slice(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const transformedArguments = command.transformArguments(...args);
|
||||||
return command.transformReply(
|
return command.transformReply(
|
||||||
await this.#sendCommand(
|
await this.#sendCommand(
|
||||||
command.transformArguments(...args),
|
transformedArguments,
|
||||||
options
|
options
|
||||||
)
|
),
|
||||||
|
transformedArguments.preserve
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -67,14 +67,21 @@ export default class RedisCluster<M extends RedisModules = RedisModules, S exten
|
|||||||
|
|
||||||
for (const [name, script] of Object.entries(this.#options.scripts)) {
|
for (const [name, script] of Object.entries(this.#options.scripts)) {
|
||||||
(this as any)[name] = async function (...args: Parameters<typeof script.transformArguments>): Promise<ReturnType<typeof script.transformReply>> {
|
(this as any)[name] = async function (...args: Parameters<typeof script.transformArguments>): Promise<ReturnType<typeof script.transformReply>> {
|
||||||
const options = isCommandOptions(args[0]) && args[0];
|
let options;
|
||||||
|
if (isCommandOptions<ClientCommandOptions>(args[0])) {
|
||||||
|
options = args[0];
|
||||||
|
args = args.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const transformedArguments = script.transformArguments(...args);
|
||||||
return script.transformReply(
|
return script.transformReply(
|
||||||
await this.executeScript(
|
await this.executeScript(
|
||||||
script,
|
script,
|
||||||
args,
|
args,
|
||||||
script.transformArguments(...(options ? args.slice(1) : args)),
|
transformedArguments,
|
||||||
options
|
options
|
||||||
)
|
),
|
||||||
|
transformedArguments.preserve
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -121,7 +128,8 @@ export default class RedisCluster<M extends RedisModules = RedisModules, S exten
|
|||||||
command.IS_READ_ONLY,
|
command.IS_READ_ONLY,
|
||||||
redisArgs,
|
redisArgs,
|
||||||
options
|
options
|
||||||
)
|
),
|
||||||
|
redisArgs.preserve
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
95
lib/commands/GEOADD.spec.ts
Normal file
95
lib/commands/GEOADD.spec.ts
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import { strict as assert } from 'assert';
|
||||||
|
import { TestRedisServers, itWithClient, TestRedisClusters, itWithCluster } from '../test-utils';
|
||||||
|
import { transformArguments } from './GEOADD';
|
||||||
|
|
||||||
|
describe('GEOADD', () => {
|
||||||
|
describe('transformArguments', () => {
|
||||||
|
it('one member', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
transformArguments('key', {
|
||||||
|
member: 'member',
|
||||||
|
longitude: 1,
|
||||||
|
latitude: 2
|
||||||
|
}),
|
||||||
|
['GEOADD', 'key', '1', '2', 'member']
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('multiple members', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
transformArguments('key', [{
|
||||||
|
longitude: 1,
|
||||||
|
latitude: 2,
|
||||||
|
member: '3',
|
||||||
|
}, {
|
||||||
|
longitude: 4,
|
||||||
|
latitude: 5,
|
||||||
|
member: '6',
|
||||||
|
}]),
|
||||||
|
['GEOADD', 'key', '1', '2', '3', '4', '5', '6']
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('with NX', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
transformArguments('key', {
|
||||||
|
longitude: 1,
|
||||||
|
latitude: 2,
|
||||||
|
member: 'member'
|
||||||
|
}, {
|
||||||
|
NX: true
|
||||||
|
}),
|
||||||
|
['GEOADD', 'key', 'NX', '1', '2', 'member']
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('with CH', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
transformArguments('key', {
|
||||||
|
longitude: 1,
|
||||||
|
latitude: 2,
|
||||||
|
member: 'member'
|
||||||
|
}, {
|
||||||
|
CH: true
|
||||||
|
}),
|
||||||
|
['GEOADD', 'key', 'CH', '1', '2', 'member']
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('with XX, CH', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
transformArguments('key', {
|
||||||
|
longitude: 1,
|
||||||
|
latitude: 2,
|
||||||
|
member: 'member'
|
||||||
|
}, {
|
||||||
|
XX: true,
|
||||||
|
CH: true
|
||||||
|
}),
|
||||||
|
['GEOADD', 'key', 'XX', 'CH', '1', '2', 'member']
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
itWithClient(TestRedisServers.OPEN, 'client.geoAdd', async client => {
|
||||||
|
assert.equal(
|
||||||
|
await client.geoAdd('key', {
|
||||||
|
member: 'member',
|
||||||
|
longitude: 1,
|
||||||
|
latitude: 2
|
||||||
|
}),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
itWithCluster(TestRedisClusters.OPEN, 'cluster.geoAdd', async cluster => {
|
||||||
|
assert.equal(
|
||||||
|
await cluster.geoAdd('key', {
|
||||||
|
member: 'member',
|
||||||
|
longitude: 1,
|
||||||
|
latitude: 2
|
||||||
|
}),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
49
lib/commands/GEOADD.ts
Normal file
49
lib/commands/GEOADD.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { GeoCoordinates, transformReplyNumber } from './generic-transformers';
|
||||||
|
|
||||||
|
interface GeoMember extends GeoCoordinates {
|
||||||
|
member: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NX {
|
||||||
|
NX?: true;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface XX {
|
||||||
|
XX?: true;
|
||||||
|
}
|
||||||
|
|
||||||
|
type SetGuards = NX | XX;
|
||||||
|
|
||||||
|
interface GeoAddCommonOptions {
|
||||||
|
CH?: true;
|
||||||
|
}
|
||||||
|
|
||||||
|
type GeoAddOptions = SetGuards & GeoAddCommonOptions;
|
||||||
|
|
||||||
|
export const FIRST_KEY_INDEX = 1;
|
||||||
|
|
||||||
|
export function transformArguments(key: string, toAdd: GeoMember | Array<GeoMember>, options?: GeoAddOptions): Array<string> {
|
||||||
|
const args = ['GEOADD', key];
|
||||||
|
|
||||||
|
if ((options as NX)?.NX) {
|
||||||
|
args.push('NX');
|
||||||
|
} else if ((options as XX)?.XX) {
|
||||||
|
args.push('XX');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options?.CH) {
|
||||||
|
args.push('CH');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const { longitude, latitude, member } of (Array.isArray(toAdd) ? toAdd : [toAdd])) {
|
||||||
|
args.push(
|
||||||
|
longitude.toString(),
|
||||||
|
latitude.toString(),
|
||||||
|
member
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const transformReply = transformReplyNumber;
|
26
lib/commands/GEODIST.spec.ts
Normal file
26
lib/commands/GEODIST.spec.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { strict as assert } from 'assert';
|
||||||
|
import { TestRedisServers, itWithClient, TestRedisClusters, itWithCluster } from '../test-utils';
|
||||||
|
import { transformArguments } from './GEODIST';
|
||||||
|
|
||||||
|
describe('GEODIST', () => {
|
||||||
|
it('transformArguments', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
transformArguments('key', '1', '2'),
|
||||||
|
['GEODIST', 'key', '1', '2']
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
itWithClient(TestRedisServers.OPEN, 'client.geoDist', async client => {
|
||||||
|
assert.equal(
|
||||||
|
await client.geoDist('key', '1', '2'),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
itWithCluster(TestRedisClusters.OPEN, 'cluster.geoDist', async cluster => {
|
||||||
|
assert.equal(
|
||||||
|
await cluster.geoDist('key', '1', '2'),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
24
lib/commands/GEODIST.ts
Normal file
24
lib/commands/GEODIST.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { GeoUnits } from './generic-transformers';
|
||||||
|
|
||||||
|
export const FIRST_KEY_INDEX = 1;
|
||||||
|
|
||||||
|
export const IS_READ_ONLY = true;
|
||||||
|
|
||||||
|
export function transformArguments(
|
||||||
|
key: string,
|
||||||
|
member1: string,
|
||||||
|
member2: string,
|
||||||
|
unit?: GeoUnits
|
||||||
|
): Array<string> {
|
||||||
|
const args = ['GEODIST', key, member1, member2];
|
||||||
|
|
||||||
|
if (unit) {
|
||||||
|
args.push(unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function transformReply(reply: string | null): number | null {
|
||||||
|
return reply === null ? null : Number(reply);
|
||||||
|
}
|
35
lib/commands/GEOHASH.spec.ts
Normal file
35
lib/commands/GEOHASH.spec.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { strict as assert } from 'assert';
|
||||||
|
import { TestRedisServers, itWithClient, TestRedisClusters, itWithCluster } from '../test-utils';
|
||||||
|
import { transformArguments } from './GEOHASH';
|
||||||
|
|
||||||
|
describe('GEOHASH', () => {
|
||||||
|
describe('transformArguments', () => {
|
||||||
|
it('single member', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
transformArguments('key', 'member'),
|
||||||
|
['GEOHASH', 'key', 'member']
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('multiple members', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
transformArguments('key', ['1', '2']),
|
||||||
|
['GEOHASH', 'key', '1', '2']
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
itWithClient(TestRedisServers.OPEN, 'client.geoHash', async client => {
|
||||||
|
assert.deepEqual(
|
||||||
|
await client.geoHash('key', 'member'),
|
||||||
|
[null]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
itWithCluster(TestRedisClusters.OPEN, 'cluster.geoHash', async cluster => {
|
||||||
|
assert.deepEqual(
|
||||||
|
await cluster.geoHash('key', 'member'),
|
||||||
|
[null]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
19
lib/commands/GEOHASH.ts
Normal file
19
lib/commands/GEOHASH.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { transformReplyStringArray } from './generic-transformers';
|
||||||
|
|
||||||
|
export const FIRST_KEY_INDEX = 1;
|
||||||
|
|
||||||
|
export const IS_READ_ONLY = true;
|
||||||
|
|
||||||
|
export function transformArguments(key: string, member: string | Array<string>): Array<string> {
|
||||||
|
const args = ['GEOHASH', key];
|
||||||
|
|
||||||
|
if (typeof member === 'string') {
|
||||||
|
args.push(member);
|
||||||
|
} else {
|
||||||
|
args.push(...member);
|
||||||
|
}
|
||||||
|
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const transformReply = transformReplyStringArray;
|
35
lib/commands/GEOPOS.spec.ts
Normal file
35
lib/commands/GEOPOS.spec.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { strict as assert } from 'assert';
|
||||||
|
import { TestRedisServers, itWithClient, TestRedisClusters, itWithCluster } from '../test-utils';
|
||||||
|
import { transformArguments } from './GEOPOS';
|
||||||
|
|
||||||
|
describe('GEOPOS', () => {
|
||||||
|
describe('transformArguments', () => {
|
||||||
|
it('single member', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
transformArguments('key', 'member'),
|
||||||
|
['GEOPOS', 'key', 'member']
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('multiple members', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
transformArguments('key', ['1', '2']),
|
||||||
|
['GEOPOS', 'key', '1', '2']
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
itWithClient(TestRedisServers.OPEN, 'client.geoPos', async client => {
|
||||||
|
assert.deepEqual(
|
||||||
|
await client.geoPos('key', 'member'),
|
||||||
|
[null]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
itWithCluster(TestRedisClusters.OPEN, 'cluster.geoPos', async cluster => {
|
||||||
|
assert.deepEqual(
|
||||||
|
await cluster.geoPos('key', 'member'),
|
||||||
|
[null]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
27
lib/commands/GEOPOS.ts
Normal file
27
lib/commands/GEOPOS.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
export const FIRST_KEY_INDEX = 1;
|
||||||
|
|
||||||
|
export const IS_READ_ONLY = true;
|
||||||
|
|
||||||
|
export function transformArguments(key: string, member: string | Array<string>): Array<string> {
|
||||||
|
const args = ['GEOPOS', key];
|
||||||
|
|
||||||
|
if (typeof member === 'string') {
|
||||||
|
args.push(member);
|
||||||
|
} else {
|
||||||
|
args.push(...member);
|
||||||
|
}
|
||||||
|
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GeoCoordinates {
|
||||||
|
longitude: string;
|
||||||
|
latitude: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function transformReply(reply: Array<[string, string] | null>): Array<GeoCoordinates | null> {
|
||||||
|
return reply.map(coordinates => coordinates === null ? null : {
|
||||||
|
longitude: coordinates[0],
|
||||||
|
latitude: coordinates[1]
|
||||||
|
});
|
||||||
|
}
|
35
lib/commands/GEOSEARCH.spec.ts
Normal file
35
lib/commands/GEOSEARCH.spec.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { strict as assert } from 'assert';
|
||||||
|
import { TestRedisServers, itWithClient, TestRedisClusters, itWithCluster } from '../test-utils';
|
||||||
|
import { transformArguments } from './GEOSEARCH';
|
||||||
|
|
||||||
|
describe('GEOSEARCH', () => {
|
||||||
|
it('transformArguments', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
transformArguments('key', 'member', {
|
||||||
|
radius: 1,
|
||||||
|
unit: 'm'
|
||||||
|
}),
|
||||||
|
['GEOSEARCH', 'key', 'FROMMEMBER', 'member', 'BYRADIUS', '1', 'm']
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
itWithClient(TestRedisServers.OPEN, 'client.geoSearch', async client => {
|
||||||
|
assert.deepEqual(
|
||||||
|
await client.geoSearch('key', 'member', {
|
||||||
|
radius: 1,
|
||||||
|
unit: 'm'
|
||||||
|
}),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
itWithCluster(TestRedisClusters.OPEN, 'cluster.geoSearch', async cluster => {
|
||||||
|
assert.deepEqual(
|
||||||
|
await cluster.geoSearch('key', 'member', {
|
||||||
|
radius: 1,
|
||||||
|
unit: 'm'
|
||||||
|
}),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
16
lib/commands/GEOSEARCH.ts
Normal file
16
lib/commands/GEOSEARCH.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { GeoSearchFrom, GeoSearchBy, GeoSearchOptions, pushGeoSearchArguments, transformReplyStringArray } from './generic-transformers';
|
||||||
|
|
||||||
|
export const FIRST_KEY_INDEX = 1;
|
||||||
|
|
||||||
|
export const IS_READ_ONLY = true;
|
||||||
|
|
||||||
|
export function transformArguments(
|
||||||
|
key: string,
|
||||||
|
from: GeoSearchFrom,
|
||||||
|
by: GeoSearchBy,
|
||||||
|
options?: GeoSearchOptions
|
||||||
|
): Array<string> {
|
||||||
|
return pushGeoSearchArguments(['GEOSEARCH'], key, from, by, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const transformReply = transformReplyStringArray;
|
41
lib/commands/GEOSEARCHSTORE.spec.ts
Normal file
41
lib/commands/GEOSEARCHSTORE.spec.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { strict as assert } from 'assert';
|
||||||
|
import { TestRedisServers, itWithClient, TestRedisClusters, itWithCluster } from '../test-utils';
|
||||||
|
import { transformArguments } from './GEOSEARCHSTORE';
|
||||||
|
|
||||||
|
describe('GEOSEARCHSTORE', () => {
|
||||||
|
it('transformArguments', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
transformArguments('destination', 'source', 'member', {
|
||||||
|
radius: 1,
|
||||||
|
unit: 'm'
|
||||||
|
}, {
|
||||||
|
SORT: 'ASC',
|
||||||
|
COUNT: {
|
||||||
|
value: 1,
|
||||||
|
ANY: true
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
['GEOSEARCHSTORE', 'destination', 'source', 'FROMMEMBER', 'member', 'BYRADIUS', '1', 'm', 'ASC', 'COUNT', '1', 'ANY']
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
itWithClient(TestRedisServers.OPEN, 'client.geoSearchStore', async client => {
|
||||||
|
assert.equal(
|
||||||
|
await client.geoSearchStore('source', 'destination', 'member', {
|
||||||
|
radius: 1,
|
||||||
|
unit: 'm'
|
||||||
|
}),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
itWithCluster(TestRedisClusters.OPEN, 'cluster.geoSearchStore', async cluster => {
|
||||||
|
assert.equal(
|
||||||
|
await cluster.geoSearchStore('{tag}source', '{tag}destination', 'member', {
|
||||||
|
radius: 1,
|
||||||
|
unit: 'm'
|
||||||
|
}),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
40
lib/commands/GEOSEARCHSTORE.ts
Normal file
40
lib/commands/GEOSEARCHSTORE.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { GeoSearchFrom, GeoSearchBy, GeoSearchOptions, pushGeoSearchArguments, transformReplyNumber } from './generic-transformers';
|
||||||
|
|
||||||
|
export const FIRST_KEY_INDEX = 1;
|
||||||
|
|
||||||
|
export const IS_READ_ONLY = true;
|
||||||
|
|
||||||
|
interface GeoSearchStoreOptions extends GeoSearchOptions {
|
||||||
|
STOREDIST?: true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function transformArguments(
|
||||||
|
destination: string,
|
||||||
|
source: string,
|
||||||
|
from: GeoSearchFrom,
|
||||||
|
by: GeoSearchBy,
|
||||||
|
options?: GeoSearchStoreOptions
|
||||||
|
): Array<string> {
|
||||||
|
const args = pushGeoSearchArguments(
|
||||||
|
['GEOSEARCHSTORE', destination],
|
||||||
|
source,
|
||||||
|
from,
|
||||||
|
by,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
|
||||||
|
if (options?.STOREDIST) {
|
||||||
|
args.push('STOREDIST');
|
||||||
|
}
|
||||||
|
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// in versions 6.2.0-6.2.4 Redis will return an empty array when `src` is empty
|
||||||
|
// TODO: issue/PR
|
||||||
|
export function transformReply(reply: number | []): number {
|
||||||
|
if (typeof reply === 'number') return reply;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
40
lib/commands/GEOSEARCH_WITH.spec.ts
Normal file
40
lib/commands/GEOSEARCH_WITH.spec.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { strict as assert } from 'assert';
|
||||||
|
import { TransformArgumentsReply } from '.';
|
||||||
|
import { TestRedisServers, itWithClient, TestRedisClusters, itWithCluster } from '../test-utils';
|
||||||
|
import { GeoReplyWith } from './generic-transformers';
|
||||||
|
import { transformArguments } from './GEOSEARCH_WITH';
|
||||||
|
|
||||||
|
describe('GEOSEARCH WITH', () => {
|
||||||
|
it('transformArguments', () => {
|
||||||
|
const expectedReply: TransformArgumentsReply = ['GEOSEARCH', 'key', 'FROMMEMBER', 'member', 'BYRADIUS', '1', 'm', 'WITHDIST']
|
||||||
|
expectedReply.preserve = ['WITHDIST'];
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
transformArguments('key', 'member', {
|
||||||
|
radius: 1,
|
||||||
|
unit: 'm'
|
||||||
|
}, [GeoReplyWith.DISTANCE]),
|
||||||
|
expectedReply
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
itWithClient(TestRedisServers.OPEN, 'client.geoSearchWith', async client => {
|
||||||
|
assert.deepEqual(
|
||||||
|
await client.geoSearchWith('key', 'member', {
|
||||||
|
radius: 1,
|
||||||
|
unit: 'm'
|
||||||
|
}, [GeoReplyWith.DISTANCE]),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
itWithCluster(TestRedisClusters.OPEN, 'cluster.geoSearchWith', async cluster => {
|
||||||
|
assert.deepEqual(
|
||||||
|
await cluster.geoSearchWith('key', 'member', {
|
||||||
|
radius: 1,
|
||||||
|
unit: 'm'
|
||||||
|
}, [GeoReplyWith.DISTANCE]),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
23
lib/commands/GEOSEARCH_WITH.ts
Normal file
23
lib/commands/GEOSEARCH_WITH.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { TransformArgumentsReply } from '.';
|
||||||
|
import { GeoSearchFrom, GeoSearchBy, GeoReplyWith, GeoSearchOptions, transformGeoMembersWithReply } from './generic-transformers';
|
||||||
|
import { transformArguments as geoSearchTransformArguments } from './GEOSEARCH';
|
||||||
|
|
||||||
|
export { FIRST_KEY_INDEX, IS_READ_ONLY } from './GEOSEARCH';
|
||||||
|
|
||||||
|
export function transformArguments(
|
||||||
|
key: string,
|
||||||
|
from: GeoSearchFrom,
|
||||||
|
by: GeoSearchBy,
|
||||||
|
replyWith: Array<GeoReplyWith>,
|
||||||
|
options?: GeoSearchOptions
|
||||||
|
): TransformArgumentsReply {
|
||||||
|
const args: TransformArgumentsReply = geoSearchTransformArguments(key, from, by, options);
|
||||||
|
|
||||||
|
args.push(...replyWith);
|
||||||
|
|
||||||
|
args.preserve = replyWith;
|
||||||
|
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const transformReply = transformGeoMembersWithReply;
|
@@ -1,15 +1,14 @@
|
|||||||
import { ScanOptions, transformScanArguments } from './generic-transformers';
|
import { ScanOptions, pushScanArguments } from './generic-transformers';
|
||||||
|
|
||||||
export const FIRST_KEY_INDEX = 1;
|
export const FIRST_KEY_INDEX = 1;
|
||||||
|
|
||||||
export const IS_READ_ONLY = true;
|
export const IS_READ_ONLY = true;
|
||||||
|
|
||||||
export function transformArguments(key: string, cursor: number, options?: ScanOptions): Array<string> {
|
export function transformArguments(key: string, cursor: number, options?: ScanOptions): Array<string> {
|
||||||
return [
|
return pushScanArguments([
|
||||||
'HSCAN',
|
'HSCAN',
|
||||||
key,
|
key
|
||||||
...transformScanArguments(cursor, options)
|
], cursor, options);
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HScanTuple {
|
export interface HScanTuple {
|
||||||
@@ -30,7 +29,7 @@ export function transformReply([cursor, rawTuples]: [string, Array<string>]): HS
|
|||||||
value: rawTuples[i + 1]
|
value: rawTuples[i + 1]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
cursor: Number(cursor),
|
cursor: Number(cursor),
|
||||||
tuples: parsedTuples
|
tuples: parsedTuples
|
||||||
|
@@ -1,21 +1,17 @@
|
|||||||
import { ScanOptions, transformScanArguments } from './generic-transformers';
|
import { ScanOptions, pushScanArguments } from './generic-transformers';
|
||||||
|
|
||||||
export const IS_READ_ONLY = true;
|
export const IS_READ_ONLY = true;
|
||||||
|
|
||||||
export interface ScanCommandOptions extends ScanOptions {
|
export interface ScanCommandOptions extends ScanOptions {
|
||||||
TYPE?: string;
|
TYPE?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function transformArguments(cursor: number, options?: ScanCommandOptions): Array<string> {
|
export function transformArguments(cursor: number, options?: ScanCommandOptions): Array<string> {
|
||||||
const args = [
|
const args = pushScanArguments(['SCAN'], cursor, options);
|
||||||
'SCAN',
|
|
||||||
...transformScanArguments(cursor, options)
|
|
||||||
];
|
|
||||||
|
|
||||||
if (options?.TYPE) {
|
if (options?.TYPE) {
|
||||||
args.push('TYPE', options.TYPE);
|
args.push('TYPE', options.TYPE);
|
||||||
}
|
}
|
||||||
|
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,13 +1,14 @@
|
|||||||
import { ScanOptions, transformScanArguments } from './generic-transformers';
|
import { ScanOptions, pushScanArguments } from './generic-transformers';
|
||||||
|
|
||||||
|
export const FIRST_KEY_INDEX = 1;
|
||||||
|
|
||||||
export const IS_READ_ONLY = true;
|
export const IS_READ_ONLY = true;
|
||||||
|
|
||||||
export function transformArguments(key: string, cursor: number, options?: ScanOptions): Array<string> {
|
export function transformArguments(key: string, cursor: number, options?: ScanOptions): Array<string> {
|
||||||
return [
|
return pushScanArguments([
|
||||||
'SSCAN',
|
'SSCAN',
|
||||||
key,
|
key,
|
||||||
...transformScanArguments(cursor, options)
|
], cursor, options);
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SScanReply {
|
interface SScanReply {
|
||||||
|
@@ -1,15 +1,14 @@
|
|||||||
import { ScanOptions, transformReplyNumberInfinity, transformScanArguments, ZMember } from './generic-transformers';
|
import { ScanOptions, transformReplyNumberInfinity, pushScanArguments, ZMember } from './generic-transformers';
|
||||||
|
|
||||||
export const FIRST_KEY_INDEX = 1;
|
export const FIRST_KEY_INDEX = 1;
|
||||||
|
|
||||||
export const IS_READ_ONLY = true;
|
export const IS_READ_ONLY = true;
|
||||||
|
|
||||||
export function transformArguments(key: string, cursor: number, options?: ScanOptions): Array<string> {
|
export function transformArguments(key: string, cursor: number, options?: ScanOptions): Array<string> {
|
||||||
return [
|
return pushScanArguments([
|
||||||
'ZSCAN',
|
'ZSCAN',
|
||||||
key,
|
key
|
||||||
...transformScanArguments(cursor, options)
|
], cursor, options);
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ZScanReply {
|
interface ZScanReply {
|
||||||
@@ -25,7 +24,7 @@ export function transformReply([cursor, rawMembers]: [string, Array<string>]): Z
|
|||||||
score: transformReplyNumberInfinity(rawMembers[i + 1])
|
score: transformReplyNumberInfinity(rawMembers[i + 1])
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
cursor: Number(cursor),
|
cursor: Number(cursor),
|
||||||
members: parsedMembers
|
members: parsedMembers
|
||||||
|
@@ -1,8 +1,9 @@
|
|||||||
import { strict as assert } from 'assert';
|
import { strict as assert } from 'assert';
|
||||||
|
import { isKeyObject } from 'util/types';
|
||||||
import {
|
import {
|
||||||
transformReplyBoolean,
|
transformReplyBoolean,
|
||||||
transformReplyBooleanArray,
|
transformReplyBooleanArray,
|
||||||
transformScanArguments,
|
pushScanArguments,
|
||||||
transformReplyNumberInfinity,
|
transformReplyNumberInfinity,
|
||||||
transformReplyNumberInfinityArray,
|
transformReplyNumberInfinityArray,
|
||||||
transformReplyNumberInfinityNull,
|
transformReplyNumberInfinityNull,
|
||||||
@@ -12,7 +13,11 @@ import {
|
|||||||
transformReplyStreamMessages,
|
transformReplyStreamMessages,
|
||||||
transformReplyStreamsMessages,
|
transformReplyStreamsMessages,
|
||||||
transformReplyStreamsMessagesNull,
|
transformReplyStreamsMessagesNull,
|
||||||
transformReplySortedSetWithScores
|
transformReplySortedSetWithScores,
|
||||||
|
pushGeoCountArgument,
|
||||||
|
pushGeoSearchArguments,
|
||||||
|
transformGeoMembersWithReply,
|
||||||
|
GeoReplyWith
|
||||||
} from './generic-transformers';
|
} from './generic-transformers';
|
||||||
|
|
||||||
describe('Generic Transformers', () => {
|
describe('Generic Transformers', () => {
|
||||||
@@ -48,17 +53,17 @@ describe('Generic Transformers', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('transformScanArguments', () => {
|
describe('pushScanArguments', () => {
|
||||||
it('cusror only', () => {
|
it('cusror only', () => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
transformScanArguments(0),
|
pushScanArguments([], 0),
|
||||||
['0']
|
['0']
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('with MATCH', () => {
|
it('with MATCH', () => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
transformScanArguments(0, {
|
pushScanArguments([], 0, {
|
||||||
MATCH: 'pattern'
|
MATCH: 'pattern'
|
||||||
}),
|
}),
|
||||||
['0', 'MATCH', 'pattern']
|
['0', 'MATCH', 'pattern']
|
||||||
@@ -67,7 +72,7 @@ describe('Generic Transformers', () => {
|
|||||||
|
|
||||||
it('with COUNT', () => {
|
it('with COUNT', () => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
transformScanArguments(0, {
|
pushScanArguments([], 0, {
|
||||||
COUNT: 1
|
COUNT: 1
|
||||||
}),
|
}),
|
||||||
['0', 'COUNT', '1']
|
['0', 'COUNT', '1']
|
||||||
@@ -76,7 +81,7 @@ describe('Generic Transformers', () => {
|
|||||||
|
|
||||||
it('with MATCH & COUNT', () => {
|
it('with MATCH & COUNT', () => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
transformScanArguments(0, {
|
pushScanArguments([], 0, {
|
||||||
MATCH: 'pattern',
|
MATCH: 'pattern',
|
||||||
COUNT: 1
|
COUNT: 1
|
||||||
}),
|
}),
|
||||||
@@ -99,7 +104,7 @@ describe('Generic Transformers', () => {
|
|||||||
Infinity
|
Infinity
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('-inf', () => {
|
it('-inf', () => {
|
||||||
assert.equal(
|
assert.equal(
|
||||||
transformReplyNumberInfinity('-inf'),
|
transformReplyNumberInfinity('-inf'),
|
||||||
@@ -271,4 +276,190 @@ describe('Generic Transformers', () => {
|
|||||||
}]
|
}]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('pushGeoCountArgument', () => {
|
||||||
|
it('undefined', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
pushGeoCountArgument([], undefined),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('number', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
pushGeoCountArgument([], 1),
|
||||||
|
['COUNT', '1']
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('with ANY', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
pushGeoCountArgument([], {
|
||||||
|
value: 1,
|
||||||
|
ANY: true
|
||||||
|
}),
|
||||||
|
['COUNT', '1', 'ANY']
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('pushGeoSearchArguments', () => {
|
||||||
|
it('FROMMEMBER, BYRADIUS', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
pushGeoSearchArguments([], 'key', 'member', {
|
||||||
|
radius: 1,
|
||||||
|
unit: 'm'
|
||||||
|
}),
|
||||||
|
['key', 'FROMMEMBER', 'member', 'BYRADIUS', '1', 'm']
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('FROMLONLAT, BYBOX', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
pushGeoSearchArguments([], 'key', {
|
||||||
|
longitude: 1,
|
||||||
|
latitude: 2
|
||||||
|
}, {
|
||||||
|
width: 1,
|
||||||
|
height: 2,
|
||||||
|
unit: 'm'
|
||||||
|
}),
|
||||||
|
['key', 'FROMLONLAT', '1', '2', 'BYBOX', '1', '2', 'm']
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('with SORT', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
pushGeoSearchArguments([], 'key', 'member', {
|
||||||
|
radius: 1,
|
||||||
|
unit: 'm'
|
||||||
|
}, {
|
||||||
|
SORT: 'ASC'
|
||||||
|
}),
|
||||||
|
['key', 'FROMMEMBER', 'member', 'BYRADIUS', '1', 'm', 'ASC']
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('transformGeoMembersWithReply', () => {
|
||||||
|
it('DISTANCE', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
transformGeoMembersWithReply([
|
||||||
|
[
|
||||||
|
'1',
|
||||||
|
'2'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'3',
|
||||||
|
'4'
|
||||||
|
]
|
||||||
|
], [GeoReplyWith.DISTANCE]),
|
||||||
|
[{
|
||||||
|
member: '1',
|
||||||
|
distance: '2'
|
||||||
|
}, {
|
||||||
|
member: '3',
|
||||||
|
distance: '4'
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('HASH', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
transformGeoMembersWithReply([
|
||||||
|
[
|
||||||
|
'1',
|
||||||
|
2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'3',
|
||||||
|
4
|
||||||
|
]
|
||||||
|
], [GeoReplyWith.HASH]),
|
||||||
|
[{
|
||||||
|
member: '1',
|
||||||
|
hash: 2
|
||||||
|
}, {
|
||||||
|
member: '3',
|
||||||
|
hash: 4
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('COORDINATES', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
transformGeoMembersWithReply([
|
||||||
|
[
|
||||||
|
'1',
|
||||||
|
[
|
||||||
|
'2',
|
||||||
|
'3'
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'4',
|
||||||
|
[
|
||||||
|
'5',
|
||||||
|
'6'
|
||||||
|
]
|
||||||
|
]
|
||||||
|
], [GeoReplyWith.COORDINATES]),
|
||||||
|
[{
|
||||||
|
member: '1',
|
||||||
|
coordinates: {
|
||||||
|
longitude: '2',
|
||||||
|
latitude: '3'
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
member: '4',
|
||||||
|
coordinates: {
|
||||||
|
longitude: '5',
|
||||||
|
latitude: '6'
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('DISTANCE, HASH, COORDINATES', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
transformGeoMembersWithReply([
|
||||||
|
[
|
||||||
|
'1',
|
||||||
|
'2',
|
||||||
|
3,
|
||||||
|
[
|
||||||
|
'4',
|
||||||
|
'5'
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'6',
|
||||||
|
'7',
|
||||||
|
8,
|
||||||
|
[
|
||||||
|
'9',
|
||||||
|
'10'
|
||||||
|
]
|
||||||
|
]
|
||||||
|
], [GeoReplyWith.DISTANCE, GeoReplyWith.HASH, GeoReplyWith.COORDINATES]),
|
||||||
|
[{
|
||||||
|
member: '1',
|
||||||
|
distance: '2',
|
||||||
|
hash: 3,
|
||||||
|
coordinates: {
|
||||||
|
longitude: '4',
|
||||||
|
latitude: '5'
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
member: '6',
|
||||||
|
distance: '7',
|
||||||
|
hash: 8,
|
||||||
|
coordinates: {
|
||||||
|
longitude: '9',
|
||||||
|
latitude: '10'
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -51,8 +51,8 @@ export interface ScanOptions {
|
|||||||
COUNT?: number;
|
COUNT?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function transformScanArguments(cursor: number, options?: ScanOptions): Array<string> {
|
export function pushScanArguments(args: Array<string>, cursor: number, options?: ScanOptions): Array<string> {
|
||||||
const args = [cursor.toString()];
|
args.push(cursor.toString());
|
||||||
|
|
||||||
if (options?.MATCH) {
|
if (options?.MATCH) {
|
||||||
args.push('MATCH', options.MATCH);
|
args.push('MATCH', options.MATCH);
|
||||||
@@ -61,7 +61,7 @@ export function transformScanArguments(cursor: number, options?: ScanOptions): A
|
|||||||
if (options?.COUNT) {
|
if (options?.COUNT) {
|
||||||
args.push('COUNT', options.COUNT.toString());
|
args.push('COUNT', options.COUNT.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ export function transformReplyNumberInfinity(reply: string): number {
|
|||||||
switch (reply) {
|
switch (reply) {
|
||||||
case '+inf':
|
case '+inf':
|
||||||
return Infinity;
|
return Infinity;
|
||||||
|
|
||||||
case '-inf':
|
case '-inf':
|
||||||
return -Infinity;
|
return -Infinity;
|
||||||
|
|
||||||
@@ -96,7 +96,7 @@ export function transformArgumentNumberInfinity(num: number): string {
|
|||||||
switch (num) {
|
switch (num) {
|
||||||
case Infinity:
|
case Infinity:
|
||||||
return '+inf';
|
return '+inf';
|
||||||
|
|
||||||
case -Infinity:
|
case -Infinity:
|
||||||
return '-inf';
|
return '-inf';
|
||||||
|
|
||||||
@@ -180,3 +180,134 @@ export function transformReplySortedSetWithScores(reply: Array<string>): Array<Z
|
|||||||
|
|
||||||
return members;
|
return members;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GeoCountArgument = number | {
|
||||||
|
value: number;
|
||||||
|
ANY?: true
|
||||||
|
};
|
||||||
|
|
||||||
|
export function pushGeoCountArgument(args: Array<string>, count: GeoCountArgument | undefined): Array<string> {
|
||||||
|
if (typeof count === 'number') {
|
||||||
|
args.push('COUNT', count.toString());
|
||||||
|
} else if (count) {
|
||||||
|
args.push('COUNT', count.value.toString());
|
||||||
|
|
||||||
|
if (count.ANY) {
|
||||||
|
args.push('ANY');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GeoUnits = 'm' | 'km' | 'mi' | 'ft';
|
||||||
|
|
||||||
|
export interface GeoCoordinates {
|
||||||
|
longitude: string | number;
|
||||||
|
latitude: string | number;
|
||||||
|
}
|
||||||
|
|
||||||
|
type GeoSearchFromMember = string;
|
||||||
|
|
||||||
|
export type GeoSearchFrom = GeoSearchFromMember | GeoCoordinates;
|
||||||
|
|
||||||
|
interface GeoSearchByRadius {
|
||||||
|
radius: number;
|
||||||
|
unit: GeoUnits;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GeoSearchByBox {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
unit: GeoUnits;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GeoSearchBy = GeoSearchByRadius | GeoSearchByBox;
|
||||||
|
|
||||||
|
export interface GeoSearchOptions {
|
||||||
|
SORT?: 'ASC' | 'DESC';
|
||||||
|
COUNT?: GeoCountArgument;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pushGeoSearchArguments(
|
||||||
|
args: Array<string>,
|
||||||
|
key: string,
|
||||||
|
from: GeoSearchFrom,
|
||||||
|
by: GeoSearchBy,
|
||||||
|
options?: GeoSearchOptions
|
||||||
|
): Array<string> {
|
||||||
|
args.push(key);
|
||||||
|
|
||||||
|
if (typeof from === 'string') {
|
||||||
|
args.push('FROMMEMBER', from);
|
||||||
|
} else {
|
||||||
|
args.push('FROMLONLAT', from.longitude.toString(), from.latitude.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('radius' in by) {
|
||||||
|
args.push('BYRADIUS', by.radius.toString());
|
||||||
|
} else {
|
||||||
|
args.push('BYBOX', by.width.toString(), by.height.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (by.unit) {
|
||||||
|
args.push(by.unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options?.SORT) {
|
||||||
|
args.push(options?.SORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
pushGeoCountArgument(args, options?.COUNT);
|
||||||
|
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum GeoReplyWith {
|
||||||
|
DISTANCE = 'WITHDIST',
|
||||||
|
HASH = 'WITHHASH',
|
||||||
|
COORDINATES = 'WITHCOORD'
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GeoReplyWithMember {
|
||||||
|
member: string;
|
||||||
|
distance?: number;
|
||||||
|
hash?: string;
|
||||||
|
coordinates?: {
|
||||||
|
longitude: string;
|
||||||
|
latitude: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function transformGeoMembersWithReply(reply: Array<Array<any>>, replyWith: Array<GeoReplyWith>): Array<GeoReplyWithMember> {
|
||||||
|
const replyWithSet = new Set(replyWith);
|
||||||
|
|
||||||
|
let index = 0,
|
||||||
|
distanceIndex = replyWithSet.has(GeoReplyWith.DISTANCE) && ++index,
|
||||||
|
hashIndex = replyWithSet.has(GeoReplyWith.HASH) && ++index,
|
||||||
|
coordinatesIndex = replyWithSet.has(GeoReplyWith.COORDINATES) && ++index;
|
||||||
|
|
||||||
|
return reply.map(member => {
|
||||||
|
const transformedMember: GeoReplyWithMember = {
|
||||||
|
member: member[0]
|
||||||
|
};
|
||||||
|
|
||||||
|
if (distanceIndex) {
|
||||||
|
transformedMember.distance = member[distanceIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hashIndex) {
|
||||||
|
transformedMember.hash = member[hashIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (coordinatesIndex) {
|
||||||
|
const [longitude, latitude] = member[coordinatesIndex];
|
||||||
|
transformedMember.coordinates = {
|
||||||
|
longitude,
|
||||||
|
latitude
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return transformedMember;
|
||||||
|
});
|
||||||
|
}
|
@@ -50,6 +50,13 @@ import * as EXPIREAT from './EXPIREAT';
|
|||||||
import * as FAILOVER from './FAILOVER';
|
import * as FAILOVER from './FAILOVER';
|
||||||
import * as FLUSHALL from './FLUSHALL';
|
import * as FLUSHALL from './FLUSHALL';
|
||||||
import * as FLUSHDB from './FLUSHDB';
|
import * as FLUSHDB from './FLUSHDB';
|
||||||
|
import * as GEOADD from './GEOADD';
|
||||||
|
import * as GEODIST from './GEODIST';
|
||||||
|
import * as GEOHASH from './GEOHASH';
|
||||||
|
import * as GEOPOS from './GEOPOS';
|
||||||
|
import * as GEOSEARCH_WITH from './GEOSEARCH_WITH';
|
||||||
|
import * as GEOSEARCH from './GEOSEARCH';
|
||||||
|
import * as GEOSEARCHSTORE from './GEOSEARCHSTORE';
|
||||||
import * as GET from './GET';
|
import * as GET from './GET';
|
||||||
import * as GETBIT from './GETBIT';
|
import * as GETBIT from './GETBIT';
|
||||||
import * as GETDEL from './GETDEL';
|
import * as GETDEL from './GETDEL';
|
||||||
@@ -116,7 +123,7 @@ import * as RPOP_COUNT from './RPOP_COUNT';
|
|||||||
import * as RPOP from './RPOP';
|
import * as RPOP from './RPOP';
|
||||||
import * as RPOPLPUSH from './RPOPLPUSH';
|
import * as RPOPLPUSH from './RPOPLPUSH';
|
||||||
import * as RPUSH from './RPUSH';
|
import * as RPUSH from './RPUSH';
|
||||||
import * as RPUSHX from './RPUSHX';
|
import * as RPUSHX from './RPUSHX';
|
||||||
import * as SADD from './SADD';
|
import * as SADD from './SADD';
|
||||||
import * as SAVE from './SAVE';
|
import * as SAVE from './SAVE';
|
||||||
import * as SCAN from './SCAN';
|
import * as SCAN from './SCAN';
|
||||||
@@ -311,6 +318,20 @@ export default {
|
|||||||
flushAll: FLUSHALL,
|
flushAll: FLUSHALL,
|
||||||
FLUSHDB,
|
FLUSHDB,
|
||||||
flushDb: FLUSHDB,
|
flushDb: FLUSHDB,
|
||||||
|
GEOADD,
|
||||||
|
geoAdd: GEOADD,
|
||||||
|
GEODIST,
|
||||||
|
geoDist: GEODIST,
|
||||||
|
GEOHASH,
|
||||||
|
geoHash: GEOHASH,
|
||||||
|
GEOPOS,
|
||||||
|
geoPos: GEOPOS,
|
||||||
|
GEOSEARCH_WITH,
|
||||||
|
geoSearchWith: GEOSEARCH_WITH,
|
||||||
|
GEOSEARCH,
|
||||||
|
geoSearch: GEOSEARCH,
|
||||||
|
GEOSEARCHSTORE,
|
||||||
|
geoSearchStore: GEOSEARCHSTORE,
|
||||||
GET,
|
GET,
|
||||||
get: GET,
|
get: GET,
|
||||||
GETBIT,
|
GETBIT,
|
||||||
@@ -561,7 +582,7 @@ export default {
|
|||||||
ZCOUNT,
|
ZCOUNT,
|
||||||
zCount: ZCOUNT,
|
zCount: ZCOUNT,
|
||||||
ZDIFF_WITHSCORES,
|
ZDIFF_WITHSCORES,
|
||||||
zDiffWithScores: ZDIFF_WITHSCORES,
|
zDiffWithScores: ZDIFF_WITHSCORES,
|
||||||
ZDIFF,
|
ZDIFF,
|
||||||
zDiff: ZDIFF,
|
zDiff: ZDIFF,
|
||||||
ZDIFFSTORE,
|
ZDIFFSTORE,
|
||||||
@@ -624,11 +645,13 @@ export default {
|
|||||||
|
|
||||||
export type RedisReply = string | number | Array<RedisReply> | null | undefined;
|
export type RedisReply = string | number | Array<RedisReply> | null | undefined;
|
||||||
|
|
||||||
|
export type TransformArgumentsReply = Array<string> & { preserve?: unknown };
|
||||||
|
|
||||||
export interface RedisCommand {
|
export interface RedisCommand {
|
||||||
FIRST_KEY_INDEX?: number | ((...args: Array<any>) => string);
|
FIRST_KEY_INDEX?: number | ((...args: Array<any>) => string);
|
||||||
IS_READ_ONLY?: boolean;
|
IS_READ_ONLY?: boolean;
|
||||||
transformArguments(...args: Array<any>): Array<string>;
|
transformArguments(...args: Array<any>): TransformArgumentsReply;
|
||||||
transformReply(reply: RedisReply): any;
|
transformReply(reply: RedisReply, preserved: unknown): any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RedisCommands {
|
export interface RedisCommands {
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import COMMANDS from './commands';
|
import COMMANDS, { TransformArgumentsReply } from './commands';
|
||||||
import { RedisCommand, RedisModules, RedisReply } from './commands';
|
import { RedisCommand, RedisModules, RedisReply } from './commands';
|
||||||
import RedisCommandsQueue from './commands-queue';
|
import RedisCommandsQueue from './commands-queue';
|
||||||
import { RedisLuaScript, RedisLuaScripts } from './lua-script';
|
import { RedisLuaScript, RedisLuaScripts } from './lua-script';
|
||||||
@@ -24,6 +24,7 @@ export type RedisMultiCommandType<M extends RedisModules, S extends RedisLuaScri
|
|||||||
|
|
||||||
export interface MultiQueuedCommand {
|
export interface MultiQueuedCommand {
|
||||||
encodedCommand: string;
|
encodedCommand: string;
|
||||||
|
preservedArguments?: unknown;
|
||||||
transformReply?: RedisCommand['transformReply'];
|
transformReply?: RedisCommand['transformReply'];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +95,7 @@ export default class RedisMultiCommand<M extends RedisModules = RedisModules, S
|
|||||||
script.SCRIPT
|
script.SCRIPT
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.addCommand(
|
return this.addCommand(
|
||||||
[
|
[
|
||||||
...evalArgs,
|
...evalArgs,
|
||||||
@@ -166,9 +167,10 @@ export default class RedisMultiCommand<M extends RedisModules = RedisModules, S
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addCommand(args: Array<string>, transformReply?: RedisCommand['transformReply']): RedisMultiCommandType<M, S> {
|
addCommand(args: TransformArgumentsReply, transformReply?: RedisCommand['transformReply']): RedisMultiCommandType<M, S> {
|
||||||
this.#queue.push({
|
this.#queue.push({
|
||||||
encodedCommand: RedisCommandsQueue.encodeCommand(args),
|
encodedCommand: RedisCommandsQueue.encodeCommand(args),
|
||||||
|
preservedArguments: args.preserve,
|
||||||
transformReply
|
transformReply
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -224,8 +226,8 @@ export default class RedisMultiCommand<M extends RedisModules = RedisModules, S
|
|||||||
|
|
||||||
const rawReplies = await this.#executor(queue, Symbol('[RedisMultiCommand] Chain ID'));
|
const rawReplies = await this.#executor(queue, Symbol('[RedisMultiCommand] Chain ID'));
|
||||||
return (rawReplies[rawReplies.length - 1]! as Array<RedisReply>).map((reply, i) => {
|
return (rawReplies[rawReplies.length - 1]! as Array<RedisReply>).map((reply, i) => {
|
||||||
const { transformReply } = queue[i + 1];
|
const { transformReply, preservedArguments } = queue[i + 1];
|
||||||
return transformReply ? transformReply(reply) : reply;
|
return transformReply ? transformReply(reply, preservedArguments) : reply;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user