You've already forked node-redis
mirror of
https://github.com/redis/node-redis.git
synced 2025-08-06 02:15:48 +03:00
V5 bringing RESP3, Sentinel and TypeMapping to node-redis
RESP3 Support - Some commands responses in RESP3 aren't stable yet and therefore return an "untyped" ReplyUnion. Sentinel TypeMapping Correctly types Multi commands Note: some API changes to be further documented in v4-to-v5.md
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,316 +1,329 @@
|
||||
import { RedisCommandArgument, RedisCommandArguments } from '@redis/client/dist/lib/commands';
|
||||
import { pushVerdictArgument, transformTuplesReply } from '@redis/client/dist/lib/commands/generic-transformers';
|
||||
import { Params, PropertyName, pushArgumentsWithLength, pushParamsArgs, pushSortByArguments, SortByProperty } from '.';
|
||||
import { ArrayReply, BlobStringReply, Command, MapReply, NumberReply, RedisArgument, ReplyUnion, TypeMapping, UnwrapReply } from '@redis/client/dist/lib/RESP/types';
|
||||
import { RediSearchProperty } from './CREATE';
|
||||
import { FtSearchParams, pushParamsArgument } from './SEARCH';
|
||||
import { pushVariadicArgument, transformTuplesReply } from '@redis/client/dist/lib/commands/generic-transformers';
|
||||
|
||||
export enum AggregateSteps {
|
||||
GROUPBY = 'GROUPBY',
|
||||
SORTBY = 'SORTBY',
|
||||
APPLY = 'APPLY',
|
||||
LIMIT = 'LIMIT',
|
||||
FILTER = 'FILTER'
|
||||
type LoadField = RediSearchProperty | {
|
||||
identifier: RediSearchProperty;
|
||||
AS?: RedisArgument;
|
||||
}
|
||||
|
||||
interface AggregateStep<T extends AggregateSteps> {
|
||||
type: T;
|
||||
export const FT_AGGREGATE_STEPS = {
|
||||
GROUPBY: 'GROUPBY',
|
||||
SORTBY: 'SORTBY',
|
||||
APPLY: 'APPLY',
|
||||
LIMIT: 'LIMIT',
|
||||
FILTER: 'FILTER'
|
||||
} as const;
|
||||
|
||||
type FT_AGGREGATE_STEPS = typeof FT_AGGREGATE_STEPS;
|
||||
|
||||
export type FtAggregateStep = FT_AGGREGATE_STEPS[keyof FT_AGGREGATE_STEPS];
|
||||
|
||||
interface AggregateStep<T extends FtAggregateStep> {
|
||||
type: T;
|
||||
}
|
||||
|
||||
export enum AggregateGroupByReducers {
|
||||
COUNT = 'COUNT',
|
||||
COUNT_DISTINCT = 'COUNT_DISTINCT',
|
||||
COUNT_DISTINCTISH = 'COUNT_DISTINCTISH',
|
||||
SUM = 'SUM',
|
||||
MIN = 'MIN',
|
||||
MAX = 'MAX',
|
||||
AVG = 'AVG',
|
||||
STDDEV = 'STDDEV',
|
||||
QUANTILE = 'QUANTILE',
|
||||
TOLIST = 'TOLIST',
|
||||
TO_LIST = 'TOLIST',
|
||||
FIRST_VALUE = 'FIRST_VALUE',
|
||||
RANDOM_SAMPLE = 'RANDOM_SAMPLE'
|
||||
export const FT_AGGREGATE_GROUP_BY_REDUCERS = {
|
||||
COUNT: 'COUNT',
|
||||
COUNT_DISTINCT: 'COUNT_DISTINCT',
|
||||
COUNT_DISTINCTISH: 'COUNT_DISTINCTISH',
|
||||
SUM: 'SUM',
|
||||
MIN: 'MIN',
|
||||
MAX: 'MAX',
|
||||
AVG: 'AVG',
|
||||
STDDEV: 'STDDEV',
|
||||
QUANTILE: 'QUANTILE',
|
||||
TOLIST: 'TOLIST',
|
||||
FIRST_VALUE: 'FIRST_VALUE',
|
||||
RANDOM_SAMPLE: 'RANDOM_SAMPLE'
|
||||
} as const;
|
||||
|
||||
type FT_AGGREGATE_GROUP_BY_REDUCERS = typeof FT_AGGREGATE_GROUP_BY_REDUCERS;
|
||||
|
||||
export type FtAggregateGroupByReducer = FT_AGGREGATE_GROUP_BY_REDUCERS[keyof FT_AGGREGATE_GROUP_BY_REDUCERS];
|
||||
|
||||
interface GroupByReducer<T extends FtAggregateGroupByReducer> {
|
||||
type: T;
|
||||
AS?: RedisArgument;
|
||||
}
|
||||
|
||||
interface GroupByReducer<T extends AggregateGroupByReducers> {
|
||||
type: T;
|
||||
AS?: string;
|
||||
interface GroupByReducerWithProperty<T extends FtAggregateGroupByReducer> extends GroupByReducer<T> {
|
||||
property: RediSearchProperty;
|
||||
}
|
||||
|
||||
type CountReducer = GroupByReducer<AggregateGroupByReducers.COUNT>;
|
||||
type CountReducer = GroupByReducer<FT_AGGREGATE_GROUP_BY_REDUCERS['COUNT']>;
|
||||
|
||||
interface CountDistinctReducer extends GroupByReducer<AggregateGroupByReducers.COUNT_DISTINCT> {
|
||||
property: PropertyName;
|
||||
type CountDistinctReducer = GroupByReducerWithProperty<FT_AGGREGATE_GROUP_BY_REDUCERS['COUNT_DISTINCT']>;
|
||||
|
||||
type CountDistinctishReducer = GroupByReducerWithProperty<FT_AGGREGATE_GROUP_BY_REDUCERS['COUNT_DISTINCTISH']>;
|
||||
|
||||
type SumReducer = GroupByReducerWithProperty<FT_AGGREGATE_GROUP_BY_REDUCERS['SUM']>;
|
||||
|
||||
type MinReducer = GroupByReducerWithProperty<FT_AGGREGATE_GROUP_BY_REDUCERS['MIN']>;
|
||||
|
||||
type MaxReducer = GroupByReducerWithProperty<FT_AGGREGATE_GROUP_BY_REDUCERS['MAX']>;
|
||||
|
||||
type AvgReducer = GroupByReducerWithProperty<FT_AGGREGATE_GROUP_BY_REDUCERS['AVG']>;
|
||||
|
||||
type StdDevReducer = GroupByReducerWithProperty<FT_AGGREGATE_GROUP_BY_REDUCERS['STDDEV']>;
|
||||
|
||||
interface QuantileReducer extends GroupByReducerWithProperty<FT_AGGREGATE_GROUP_BY_REDUCERS['QUANTILE']> {
|
||||
quantile: number;
|
||||
}
|
||||
|
||||
interface CountDistinctishReducer extends GroupByReducer<AggregateGroupByReducers.COUNT_DISTINCTISH> {
|
||||
property: PropertyName;
|
||||
type ToListReducer = GroupByReducerWithProperty<FT_AGGREGATE_GROUP_BY_REDUCERS['TOLIST']>;
|
||||
|
||||
interface FirstValueReducer extends GroupByReducerWithProperty<FT_AGGREGATE_GROUP_BY_REDUCERS['FIRST_VALUE']> {
|
||||
BY?: RediSearchProperty | {
|
||||
property: RediSearchProperty;
|
||||
direction?: 'ASC' | 'DESC';
|
||||
};
|
||||
}
|
||||
|
||||
interface SumReducer extends GroupByReducer<AggregateGroupByReducers.SUM> {
|
||||
property: PropertyName;
|
||||
}
|
||||
|
||||
interface MinReducer extends GroupByReducer<AggregateGroupByReducers.MIN> {
|
||||
property: PropertyName;
|
||||
}
|
||||
|
||||
interface MaxReducer extends GroupByReducer<AggregateGroupByReducers.MAX> {
|
||||
property: PropertyName;
|
||||
}
|
||||
|
||||
interface AvgReducer extends GroupByReducer<AggregateGroupByReducers.AVG> {
|
||||
property: PropertyName;
|
||||
}
|
||||
|
||||
interface StdDevReducer extends GroupByReducer<AggregateGroupByReducers.STDDEV> {
|
||||
property: PropertyName;
|
||||
}
|
||||
|
||||
interface QuantileReducer extends GroupByReducer<AggregateGroupByReducers.QUANTILE> {
|
||||
property: PropertyName;
|
||||
quantile: number;
|
||||
}
|
||||
|
||||
interface ToListReducer extends GroupByReducer<AggregateGroupByReducers.TOLIST> {
|
||||
property: PropertyName;
|
||||
}
|
||||
|
||||
interface FirstValueReducer extends GroupByReducer<AggregateGroupByReducers.FIRST_VALUE> {
|
||||
property: PropertyName;
|
||||
BY?: PropertyName | {
|
||||
property: PropertyName;
|
||||
direction?: 'ASC' | 'DESC';
|
||||
};
|
||||
}
|
||||
|
||||
interface RandomSampleReducer extends GroupByReducer<AggregateGroupByReducers.RANDOM_SAMPLE> {
|
||||
property: PropertyName;
|
||||
sampleSize: number;
|
||||
interface RandomSampleReducer extends GroupByReducerWithProperty<FT_AGGREGATE_GROUP_BY_REDUCERS['RANDOM_SAMPLE']> {
|
||||
sampleSize: number;
|
||||
}
|
||||
|
||||
type GroupByReducers = CountReducer | CountDistinctReducer | CountDistinctishReducer | SumReducer | MinReducer | MaxReducer | AvgReducer | StdDevReducer | QuantileReducer | ToListReducer | FirstValueReducer | RandomSampleReducer;
|
||||
|
||||
interface GroupByStep extends AggregateStep<AggregateSteps.GROUPBY> {
|
||||
properties?: PropertyName | Array<PropertyName>;
|
||||
REDUCE: GroupByReducers | Array<GroupByReducers>;
|
||||
interface GroupByStep extends AggregateStep<FT_AGGREGATE_STEPS['GROUPBY']> {
|
||||
properties?: RediSearchProperty | Array<RediSearchProperty>;
|
||||
REDUCE: GroupByReducers | Array<GroupByReducers>;
|
||||
}
|
||||
|
||||
interface SortStep extends AggregateStep<AggregateSteps.SORTBY> {
|
||||
BY: SortByProperty | Array<SortByProperty>;
|
||||
MAX?: number;
|
||||
type SortByProperty = RedisArgument | {
|
||||
BY: RediSearchProperty;
|
||||
DIRECTION?: 'ASC' | 'DESC';
|
||||
};
|
||||
|
||||
interface SortStep extends AggregateStep<FT_AGGREGATE_STEPS['SORTBY']> {
|
||||
BY: SortByProperty | Array<SortByProperty>;
|
||||
MAX?: number;
|
||||
}
|
||||
|
||||
interface ApplyStep extends AggregateStep<AggregateSteps.APPLY> {
|
||||
expression: string;
|
||||
AS: string;
|
||||
interface ApplyStep extends AggregateStep<FT_AGGREGATE_STEPS['APPLY']> {
|
||||
expression: RedisArgument;
|
||||
AS: RedisArgument;
|
||||
}
|
||||
|
||||
interface LimitStep extends AggregateStep<AggregateSteps.LIMIT> {
|
||||
from: number;
|
||||
size: number;
|
||||
interface LimitStep extends AggregateStep<FT_AGGREGATE_STEPS['LIMIT']> {
|
||||
from: number;
|
||||
size: number;
|
||||
}
|
||||
|
||||
interface FilterStep extends AggregateStep<AggregateSteps.FILTER> {
|
||||
expression: string;
|
||||
interface FilterStep extends AggregateStep<FT_AGGREGATE_STEPS['FILTER']> {
|
||||
expression: RedisArgument;
|
||||
}
|
||||
|
||||
type LoadField = PropertyName | {
|
||||
identifier: PropertyName;
|
||||
AS?: string;
|
||||
}
|
||||
|
||||
export interface AggregateOptions {
|
||||
VERBATIM?: boolean;
|
||||
ADDSCORES?: boolean;
|
||||
LOAD?: LoadField | Array<LoadField>;
|
||||
STEPS?: Array<GroupByStep | SortStep | ApplyStep | LimitStep | FilterStep>;
|
||||
PARAMS?: Params;
|
||||
DIALECT?: number;
|
||||
TIMEOUT?: number;
|
||||
}
|
||||
|
||||
export const FIRST_KEY_INDEX = 1;
|
||||
|
||||
export const IS_READ_ONLY = true;
|
||||
|
||||
export function transformArguments(
|
||||
index: string,
|
||||
query: string,
|
||||
options?: AggregateOptions
|
||||
): RedisCommandArguments {
|
||||
return pushAggregatehOptions(
|
||||
['FT.AGGREGATE', index, query],
|
||||
options
|
||||
);
|
||||
}
|
||||
|
||||
export function pushAggregatehOptions(
|
||||
args: RedisCommandArguments,
|
||||
options?: AggregateOptions
|
||||
): RedisCommandArguments {
|
||||
if (options?.VERBATIM) {
|
||||
args.push('VERBATIM');
|
||||
}
|
||||
|
||||
if (options?.ADDSCORES) {
|
||||
args.push('ADDSCORES');
|
||||
}
|
||||
|
||||
if (options?.LOAD) {
|
||||
args.push('LOAD');
|
||||
pushArgumentsWithLength(args, () => {
|
||||
if (Array.isArray(options.LOAD)) {
|
||||
for (const load of options.LOAD) {
|
||||
pushLoadField(args, load);
|
||||
}
|
||||
} else {
|
||||
pushLoadField(args, options.LOAD!);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (options?.STEPS) {
|
||||
for (const step of options.STEPS) {
|
||||
switch (step.type) {
|
||||
case AggregateSteps.GROUPBY:
|
||||
args.push('GROUPBY');
|
||||
if (!step.properties) {
|
||||
args.push('0');
|
||||
} else {
|
||||
pushVerdictArgument(args, step.properties);
|
||||
}
|
||||
|
||||
if (Array.isArray(step.REDUCE)) {
|
||||
for (const reducer of step.REDUCE) {
|
||||
pushGroupByReducer(args, reducer);
|
||||
}
|
||||
} else {
|
||||
pushGroupByReducer(args, step.REDUCE);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case AggregateSteps.SORTBY:
|
||||
pushSortByArguments(args, 'SORTBY', step.BY);
|
||||
|
||||
if (step.MAX) {
|
||||
args.push('MAX', step.MAX.toString());
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case AggregateSteps.APPLY:
|
||||
args.push('APPLY', step.expression, 'AS', step.AS);
|
||||
break;
|
||||
|
||||
case AggregateSteps.LIMIT:
|
||||
args.push('LIMIT', step.from.toString(), step.size.toString());
|
||||
break;
|
||||
|
||||
case AggregateSteps.FILTER:
|
||||
args.push('FILTER', step.expression);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pushParamsArgs(args, options?.PARAMS);
|
||||
|
||||
if (options?.DIALECT) {
|
||||
args.push('DIALECT', options.DIALECT.toString());
|
||||
}
|
||||
|
||||
if (options?.TIMEOUT !== undefined) {
|
||||
args.push('TIMEOUT', options.TIMEOUT.toString());
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
function pushLoadField(args: RedisCommandArguments, toLoad: LoadField): void {
|
||||
if (typeof toLoad === 'string') {
|
||||
args.push(toLoad);
|
||||
} else {
|
||||
args.push(toLoad.identifier);
|
||||
|
||||
if (toLoad.AS) {
|
||||
args.push('AS', toLoad.AS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function pushGroupByReducer(args: RedisCommandArguments, reducer: GroupByReducers): void {
|
||||
args.push('REDUCE', reducer.type);
|
||||
|
||||
switch (reducer.type) {
|
||||
case AggregateGroupByReducers.COUNT:
|
||||
args.push('0');
|
||||
break;
|
||||
|
||||
case AggregateGroupByReducers.COUNT_DISTINCT:
|
||||
case AggregateGroupByReducers.COUNT_DISTINCTISH:
|
||||
case AggregateGroupByReducers.SUM:
|
||||
case AggregateGroupByReducers.MIN:
|
||||
case AggregateGroupByReducers.MAX:
|
||||
case AggregateGroupByReducers.AVG:
|
||||
case AggregateGroupByReducers.STDDEV:
|
||||
case AggregateGroupByReducers.TOLIST:
|
||||
args.push('1', reducer.property);
|
||||
break;
|
||||
|
||||
case AggregateGroupByReducers.QUANTILE:
|
||||
args.push('2', reducer.property, reducer.quantile.toString());
|
||||
break;
|
||||
|
||||
case AggregateGroupByReducers.FIRST_VALUE: {
|
||||
pushArgumentsWithLength(args, () => {
|
||||
args.push(reducer.property);
|
||||
|
||||
if (reducer.BY) {
|
||||
args.push('BY');
|
||||
if (typeof reducer.BY === 'string') {
|
||||
args.push(reducer.BY);
|
||||
} else {
|
||||
args.push(reducer.BY.property);
|
||||
|
||||
if (reducer.BY.direction) {
|
||||
args.push(reducer.BY.direction);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case AggregateGroupByReducers.RANDOM_SAMPLE:
|
||||
args.push('2', reducer.property, reducer.sampleSize.toString());
|
||||
break;
|
||||
}
|
||||
|
||||
if (reducer.AS) {
|
||||
args.push('AS', reducer.AS);
|
||||
}
|
||||
export interface FtAggregateOptions {
|
||||
VERBATIM?: boolean;
|
||||
ADDSCORES?: boolean;
|
||||
LOAD?: LoadField | Array<LoadField>;
|
||||
TIMEOUT?: number;
|
||||
STEPS?: Array<GroupByStep | SortStep | ApplyStep | LimitStep | FilterStep>;
|
||||
PARAMS?: FtSearchParams;
|
||||
DIALECT?: number;
|
||||
}
|
||||
|
||||
export type AggregateRawReply = [
|
||||
total: number,
|
||||
...results: Array<Array<RedisCommandArgument>>
|
||||
total: UnwrapReply<NumberReply>,
|
||||
...results: UnwrapReply<ArrayReply<ArrayReply<BlobStringReply>>>
|
||||
];
|
||||
|
||||
export interface AggregateReply {
|
||||
total: number;
|
||||
results: Array<Record<string, RedisCommandArgument>>;
|
||||
}
|
||||
total: number;
|
||||
results: Array<MapReply<BlobStringReply, BlobStringReply>>;
|
||||
};
|
||||
|
||||
export function transformReply(rawReply: AggregateRawReply): AggregateReply {
|
||||
const results: Array<Record<string, RedisCommandArgument>> = [];
|
||||
for (let i = 1; i < rawReply.length; i++) {
|
||||
export default {
|
||||
FIRST_KEY_INDEX: undefined,
|
||||
IS_READ_ONLY: false,
|
||||
transformArguments(index: RedisArgument, query: RedisArgument, options?: FtAggregateOptions) {
|
||||
const args = ['FT.AGGREGATE', index, query];
|
||||
|
||||
return pushAggregateOptions(args, options);
|
||||
},
|
||||
transformReply: {
|
||||
2: (rawReply: AggregateRawReply, preserve?: any, typeMapping?: TypeMapping): AggregateReply => {
|
||||
const results: Array<MapReply<BlobStringReply, BlobStringReply>> = [];
|
||||
for (let i = 1; i < rawReply.length; i++) {
|
||||
results.push(
|
||||
transformTuplesReply(rawReply[i] as Array<RedisCommandArgument>)
|
||||
transformTuplesReply(rawReply[i] as ArrayReply<BlobStringReply>, preserve, typeMapping)
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
total: Number(rawReply[0]),
|
||||
results
|
||||
};
|
||||
},
|
||||
3: undefined as unknown as () => ReplyUnion
|
||||
},
|
||||
unstableResp3: true
|
||||
} as const satisfies Command;
|
||||
|
||||
export function pushAggregateOptions(args: Array<RedisArgument>, options?: FtAggregateOptions) {
|
||||
if (options?.VERBATIM) {
|
||||
args.push('VERBATIM');
|
||||
}
|
||||
|
||||
if (options?.ADDSCORES) {
|
||||
args.push('ADDSCORES');
|
||||
}
|
||||
|
||||
if (options?.LOAD) {
|
||||
const length = args.push('LOAD', '');
|
||||
|
||||
if (Array.isArray(options.LOAD)) {
|
||||
for (const load of options.LOAD) {
|
||||
pushLoadField(args, load);
|
||||
}
|
||||
} else {
|
||||
pushLoadField(args, options.LOAD);
|
||||
}
|
||||
|
||||
return {
|
||||
total: rawReply[0],
|
||||
results
|
||||
};
|
||||
args[length - 1] = (args.length - length).toString();
|
||||
}
|
||||
|
||||
if (options?.TIMEOUT !== undefined) {
|
||||
args.push('TIMEOUT', options.TIMEOUT.toString());
|
||||
}
|
||||
|
||||
if (options?.STEPS) {
|
||||
for (const step of options.STEPS) {
|
||||
args.push(step.type);
|
||||
switch (step.type) {
|
||||
case FT_AGGREGATE_STEPS.GROUPBY:
|
||||
if (!step.properties) {
|
||||
args.push('0');
|
||||
} else {
|
||||
pushVariadicArgument(args, step.properties);
|
||||
}
|
||||
|
||||
if (Array.isArray(step.REDUCE)) {
|
||||
for (const reducer of step.REDUCE) {
|
||||
pushGroupByReducer(args, reducer);
|
||||
}
|
||||
} else {
|
||||
pushGroupByReducer(args, step.REDUCE);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case FT_AGGREGATE_STEPS.SORTBY:
|
||||
const length = args.push('');
|
||||
|
||||
if (Array.isArray(step.BY)) {
|
||||
for (const by of step.BY) {
|
||||
pushSortByProperty(args, by);
|
||||
}
|
||||
} else {
|
||||
pushSortByProperty(args, step.BY);
|
||||
}
|
||||
|
||||
if (step.MAX) {
|
||||
args.push('MAX', step.MAX.toString());
|
||||
}
|
||||
|
||||
args[length - 1] = (args.length - length).toString();
|
||||
|
||||
break;
|
||||
|
||||
case FT_AGGREGATE_STEPS.APPLY:
|
||||
args.push(step.expression, 'AS', step.AS);
|
||||
break;
|
||||
|
||||
case FT_AGGREGATE_STEPS.LIMIT:
|
||||
args.push(step.from.toString(), step.size.toString());
|
||||
break;
|
||||
|
||||
case FT_AGGREGATE_STEPS.FILTER:
|
||||
args.push(step.expression);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pushParamsArgument(args, options?.PARAMS);
|
||||
|
||||
if (options?.DIALECT !== undefined) {
|
||||
args.push('DIALECT', options.DIALECT.toString());
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
function pushLoadField(args: Array<RedisArgument>, toLoad: LoadField) {
|
||||
if (typeof toLoad === 'string' || toLoad instanceof Buffer) {
|
||||
args.push(toLoad);
|
||||
} else {
|
||||
args.push(toLoad.identifier);
|
||||
|
||||
if (toLoad.AS) {
|
||||
args.push('AS', toLoad.AS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function pushGroupByReducer(args: Array<RedisArgument>, reducer: GroupByReducers) {
|
||||
args.push('REDUCE', reducer.type);
|
||||
|
||||
switch (reducer.type) {
|
||||
case FT_AGGREGATE_GROUP_BY_REDUCERS.COUNT:
|
||||
args.push('0');
|
||||
break;
|
||||
|
||||
case FT_AGGREGATE_GROUP_BY_REDUCERS.COUNT_DISTINCT:
|
||||
case FT_AGGREGATE_GROUP_BY_REDUCERS.COUNT_DISTINCTISH:
|
||||
case FT_AGGREGATE_GROUP_BY_REDUCERS.SUM:
|
||||
case FT_AGGREGATE_GROUP_BY_REDUCERS.MIN:
|
||||
case FT_AGGREGATE_GROUP_BY_REDUCERS.MAX:
|
||||
case FT_AGGREGATE_GROUP_BY_REDUCERS.AVG:
|
||||
case FT_AGGREGATE_GROUP_BY_REDUCERS.STDDEV:
|
||||
case FT_AGGREGATE_GROUP_BY_REDUCERS.TOLIST:
|
||||
args.push('1', reducer.property);
|
||||
break;
|
||||
|
||||
case FT_AGGREGATE_GROUP_BY_REDUCERS.QUANTILE:
|
||||
args.push('2', reducer.property, reducer.quantile.toString());
|
||||
break;
|
||||
|
||||
case FT_AGGREGATE_GROUP_BY_REDUCERS.FIRST_VALUE: {
|
||||
const length = args.push('', reducer.property) - 1;
|
||||
if (reducer.BY) {
|
||||
args.push('BY');
|
||||
if (typeof reducer.BY === 'string' || reducer.BY instanceof Buffer) {
|
||||
args.push(reducer.BY);
|
||||
} else {
|
||||
args.push(reducer.BY.property);
|
||||
if (reducer.BY.direction) {
|
||||
args.push(reducer.BY.direction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
args[length - 1] = (args.length - length).toString();
|
||||
break;
|
||||
}
|
||||
|
||||
case FT_AGGREGATE_GROUP_BY_REDUCERS.RANDOM_SAMPLE:
|
||||
args.push('2', reducer.property, reducer.sampleSize.toString());
|
||||
break;
|
||||
}
|
||||
|
||||
if (reducer.AS) {
|
||||
args.push('AS', reducer.AS);
|
||||
}
|
||||
}
|
||||
|
||||
function pushSortByProperty(args: Array<RedisArgument>, sortBy: SortByProperty) {
|
||||
if (typeof sortBy === 'string' || sortBy instanceof Buffer) {
|
||||
args.push(sortBy);
|
||||
} else {
|
||||
args.push(sortBy.BY);
|
||||
if (sortBy.DIRECTION) {
|
||||
args.push(sortBy.DIRECTION);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,37 +1,47 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { transformArguments } from './AGGREGATE_WITHCURSOR';
|
||||
import { SchemaFieldTypes } from '.';
|
||||
import AGGREGATE_WITHCURSOR from './AGGREGATE_WITHCURSOR';
|
||||
|
||||
describe('AGGREGATE WITHCURSOR', () => {
|
||||
describe('transformArguments', () => {
|
||||
it('without options', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', '*'),
|
||||
['FT.AGGREGATE', 'index', '*', 'WITHCURSOR']
|
||||
);
|
||||
});
|
||||
|
||||
it('with COUNT', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', '*', { COUNT: 1 }),
|
||||
['FT.AGGREGATE', 'index', '*', 'WITHCURSOR', 'COUNT', '1']
|
||||
);
|
||||
});
|
||||
describe('transformArguments', () => {
|
||||
it('without options', () => {
|
||||
assert.deepEqual(
|
||||
AGGREGATE_WITHCURSOR.transformArguments('index', '*'),
|
||||
['FT.AGGREGATE', 'index', '*', 'WITHCURSOR']
|
||||
);
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.aggregateWithCursor', async client => {
|
||||
await client.ft.create('index', {
|
||||
field: SchemaFieldTypes.NUMERIC
|
||||
});
|
||||
it('with COUNT', () => {
|
||||
assert.deepEqual(
|
||||
AGGREGATE_WITHCURSOR.transformArguments('index', '*', {
|
||||
COUNT: 1
|
||||
}),
|
||||
['FT.AGGREGATE', 'index', '*', 'WITHCURSOR', 'COUNT', '1']
|
||||
);
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
await client.ft.aggregateWithCursor('index', '*'),
|
||||
{
|
||||
total: 0,
|
||||
results: [],
|
||||
cursor: 0
|
||||
}
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
it('with MAXIDLE', () => {
|
||||
assert.deepEqual(
|
||||
AGGREGATE_WITHCURSOR.transformArguments('index', '*', {
|
||||
MAXIDLE: 1
|
||||
}),
|
||||
['FT.AGGREGATE', 'index', '*', 'WITHCURSOR', 'MAXIDLE', '1']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.aggregateWithCursor', async client => {
|
||||
await client.ft.create('index', {
|
||||
field: 'NUMERIC'
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
await client.ft.aggregateWithCursor('index', '*'),
|
||||
{
|
||||
total: 0,
|
||||
results: [],
|
||||
cursor: 0
|
||||
}
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
||||
|
@@ -1,44 +1,47 @@
|
||||
import {
|
||||
AggregateOptions,
|
||||
AggregateRawReply,
|
||||
AggregateReply,
|
||||
transformArguments as transformAggregateArguments,
|
||||
transformReply as transformAggregateReply
|
||||
} from './AGGREGATE';
|
||||
import { RedisArgument, Command, ReplyUnion, NumberReply } from '@redis/client/dist/lib/RESP/types';
|
||||
import AGGREGATE, { AggregateRawReply, AggregateReply, FtAggregateOptions } from './AGGREGATE';
|
||||
|
||||
export { FIRST_KEY_INDEX, IS_READ_ONLY } from './AGGREGATE';
|
||||
|
||||
interface AggregateWithCursorOptions extends AggregateOptions {
|
||||
COUNT?: number;
|
||||
export interface FtAggregateWithCursorOptions extends FtAggregateOptions {
|
||||
COUNT?: number;
|
||||
MAXIDLE?: number;
|
||||
}
|
||||
|
||||
export function transformArguments(
|
||||
index: string,
|
||||
query: string,
|
||||
options?: AggregateWithCursorOptions
|
||||
) {
|
||||
const args = transformAggregateArguments(index, query, options);
|
||||
|
||||
type AggregateWithCursorRawReply = [
|
||||
result: AggregateRawReply,
|
||||
cursor: NumberReply
|
||||
];
|
||||
|
||||
export interface AggregateWithCursorReply extends AggregateReply {
|
||||
cursor: NumberReply;
|
||||
}
|
||||
|
||||
export default {
|
||||
FIRST_KEY_INDEX: AGGREGATE.FIRST_KEY_INDEX,
|
||||
IS_READ_ONLY: AGGREGATE.IS_READ_ONLY,
|
||||
transformArguments(index: RedisArgument, query: RedisArgument, options?: FtAggregateWithCursorOptions) {
|
||||
const args = AGGREGATE.transformArguments(index, query, options);
|
||||
args.push('WITHCURSOR');
|
||||
if (options?.COUNT) {
|
||||
args.push('COUNT', options.COUNT.toString());
|
||||
|
||||
if (options?.COUNT !== undefined) {
|
||||
args.push('COUNT', options.COUNT.toString());
|
||||
}
|
||||
|
||||
if(options?.MAXIDLE !== undefined) {
|
||||
args.push('MAXIDLE', options.MAXIDLE.toString());
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
type AggregateWithCursorRawReply = [
|
||||
result: AggregateRawReply,
|
||||
cursor: number
|
||||
];
|
||||
|
||||
interface AggregateWithCursorReply extends AggregateReply {
|
||||
cursor: number;
|
||||
}
|
||||
|
||||
export function transformReply(reply: AggregateWithCursorRawReply): AggregateWithCursorReply {
|
||||
return {
|
||||
...transformAggregateReply(reply[0]),
|
||||
},
|
||||
transformReply: {
|
||||
2: (reply: AggregateWithCursorRawReply): AggregateWithCursorReply => {
|
||||
return {
|
||||
...AGGREGATE.transformReply[2](reply[0]),
|
||||
cursor: reply[1]
|
||||
};
|
||||
}
|
||||
};
|
||||
},
|
||||
3: undefined as unknown as () => ReplyUnion
|
||||
},
|
||||
unstableResp3: true
|
||||
} as const satisfies Command;
|
||||
|
||||
|
@@ -1,11 +1,24 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import { transformArguments } from './ALIASADD';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import ALIASADD from './ALIASADD';
|
||||
import { SCHEMA_FIELD_TYPE } from './CREATE';
|
||||
|
||||
describe('ALIASADD', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('alias', 'index'),
|
||||
['FT.ALIASADD', 'alias', 'index']
|
||||
);
|
||||
});
|
||||
describe('FT.ALIASADD', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
ALIASADD.transformArguments('alias', 'index'),
|
||||
['FT.ALIASADD', 'alias', 'index']
|
||||
);
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.aliasAdd', async client => {
|
||||
const [, reply] = await Promise.all([
|
||||
client.ft.create('index', {
|
||||
field: SCHEMA_FIELD_TYPE.TEXT
|
||||
}),
|
||||
client.ft.aliasAdd('alias', 'index')
|
||||
]);
|
||||
|
||||
assert.equal(reply, 'OK');
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
||||
|
@@ -1,5 +1,10 @@
|
||||
export function transformArguments(name: string, index: string): Array<string> {
|
||||
return ['FT.ALIASADD', name, index];
|
||||
}
|
||||
import { RedisArgument, SimpleStringReply, Command } from '@redis/client/dist/lib/RESP/types';
|
||||
|
||||
export declare function transformReply(): 'OK';
|
||||
export default {
|
||||
FIRST_KEY_INDEX: undefined,
|
||||
IS_READ_ONLY: true,
|
||||
transformArguments(alias: RedisArgument, index: RedisArgument) {
|
||||
return ['FT.ALIASADD', alias, index];
|
||||
},
|
||||
transformReply: undefined as unknown as () => SimpleStringReply<'OK'>
|
||||
} as const satisfies Command;
|
||||
|
@@ -1,11 +1,25 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import { transformArguments } from './ALIASDEL';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import ALIASDEL from './ALIASDEL';
|
||||
import { SCHEMA_FIELD_TYPE } from './CREATE';
|
||||
|
||||
describe('ALIASDEL', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('alias', 'index'),
|
||||
['FT.ALIASDEL', 'alias', 'index']
|
||||
);
|
||||
});
|
||||
describe('FT.ALIASDEL', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
ALIASDEL.transformArguments('alias'),
|
||||
['FT.ALIASDEL', 'alias']
|
||||
);
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.aliasAdd', async client => {
|
||||
const [, , reply] = await Promise.all([
|
||||
client.ft.create('index', {
|
||||
field: SCHEMA_FIELD_TYPE.TEXT
|
||||
}),
|
||||
client.ft.aliasAdd('alias', 'index'),
|
||||
client.ft.aliasDel('alias')
|
||||
]);
|
||||
|
||||
assert.equal(reply, 'OK');
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
||||
|
@@ -1,5 +1,10 @@
|
||||
export function transformArguments(name: string, index: string): Array<string> {
|
||||
return ['FT.ALIASDEL', name, index];
|
||||
}
|
||||
import { RedisArgument, SimpleStringReply, Command } from '@redis/client/dist/lib/RESP/types';
|
||||
|
||||
export declare function transformReply(): 'OK';
|
||||
export default {
|
||||
FIRST_KEY_INDEX: undefined,
|
||||
IS_READ_ONLY: true,
|
||||
transformArguments(alias: RedisArgument) {
|
||||
return ['FT.ALIASDEL', alias];
|
||||
},
|
||||
transformReply: undefined as unknown as () => SimpleStringReply<'OK'>
|
||||
} as const satisfies Command;
|
||||
|
@@ -1,11 +1,24 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import { transformArguments } from './ALIASUPDATE';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import ALIASUPDATE from './ALIASUPDATE';
|
||||
import { SCHEMA_FIELD_TYPE } from './CREATE';
|
||||
|
||||
describe('ALIASUPDATE', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('alias', 'index'),
|
||||
['FT.ALIASUPDATE', 'alias', 'index']
|
||||
);
|
||||
});
|
||||
describe('FT.ALIASUPDATE', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
ALIASUPDATE.transformArguments('alias', 'index'),
|
||||
['FT.ALIASUPDATE', 'alias', 'index']
|
||||
);
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.aliasUpdate', async client => {
|
||||
const [, reply] = await Promise.all([
|
||||
client.ft.create('index', {
|
||||
field: SCHEMA_FIELD_TYPE.TEXT
|
||||
}),
|
||||
client.ft.aliasUpdate('alias', 'index')
|
||||
]);
|
||||
|
||||
assert.equal(reply, 'OK');
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
||||
|
@@ -1,5 +1,10 @@
|
||||
export function transformArguments(name: string, index: string): Array<string> {
|
||||
return ['FT.ALIASUPDATE', name, index];
|
||||
}
|
||||
import { SimpleStringReply, Command, RedisArgument } from '@redis/client/dist/lib/RESP/types';
|
||||
|
||||
export declare function transformReply(): 'OK';
|
||||
export default {
|
||||
FIRST_KEY_INDEX: undefined,
|
||||
IS_READ_ONLY: true,
|
||||
transformArguments(alias: RedisArgument, index: RedisArgument) {
|
||||
return ['FT.ALIASUPDATE', alias, index];
|
||||
},
|
||||
transformReply: undefined as unknown as () => SimpleStringReply<'OK'>
|
||||
} as const satisfies Command;
|
||||
|
@@ -1,37 +1,35 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { transformArguments } from './ALTER';
|
||||
import { SchemaFieldTypes } from '.';
|
||||
import ALTER from './ALTER';
|
||||
import { SCHEMA_FIELD_TYPE } from './CREATE';
|
||||
|
||||
describe('ALTER', () => {
|
||||
describe('transformArguments', () => {
|
||||
it('with NOINDEX', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {
|
||||
field: {
|
||||
type: SchemaFieldTypes.TEXT,
|
||||
NOINDEX: true,
|
||||
SORTABLE: 'UNF',
|
||||
AS: 'text'
|
||||
}
|
||||
}),
|
||||
['FT.ALTER', 'index', 'SCHEMA', 'ADD', 'field', 'AS', 'text', 'TEXT', 'SORTABLE', 'UNF', 'NOINDEX']
|
||||
);
|
||||
});
|
||||
describe('FT.ALTER', () => {
|
||||
describe('transformArguments', () => {
|
||||
it('with NOINDEX', () => {
|
||||
assert.deepEqual(
|
||||
ALTER.transformArguments('index', {
|
||||
field: {
|
||||
type: SCHEMA_FIELD_TYPE.TEXT,
|
||||
NOINDEX: true,
|
||||
SORTABLE: 'UNF',
|
||||
AS: 'text'
|
||||
}
|
||||
}),
|
||||
['FT.ALTER', 'index', 'SCHEMA', 'ADD', 'field', 'AS', 'text', 'TEXT', 'SORTABLE', 'UNF', 'NOINDEX']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.create', async client => {
|
||||
await Promise.all([
|
||||
client.ft.create('index', {
|
||||
title: SchemaFieldTypes.TEXT
|
||||
}),
|
||||
]);
|
||||
testUtils.testWithClient('client.ft.create', async client => {
|
||||
const [, reply] = await Promise.all([
|
||||
client.ft.create('index', {
|
||||
title: SCHEMA_FIELD_TYPE.TEXT
|
||||
}),
|
||||
client.ft.alter('index', {
|
||||
body: SCHEMA_FIELD_TYPE.TEXT
|
||||
})
|
||||
]);
|
||||
|
||||
assert.equal(
|
||||
await client.ft.alter('index', {
|
||||
body: SchemaFieldTypes.TEXT
|
||||
}),
|
||||
'OK'
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
assert.equal(reply, 'OK');
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
||||
|
@@ -1,10 +1,13 @@
|
||||
import { RediSearchSchema, pushSchema } from '.';
|
||||
import { RedisArgument, SimpleStringReply, Command } from '@redis/client/dist/lib/RESP/types';
|
||||
import { RediSearchSchema, pushSchema } from './CREATE';
|
||||
|
||||
export function transformArguments(index: string, schema: RediSearchSchema): Array<string> {
|
||||
export default {
|
||||
FIRST_KEY_INDEX: undefined,
|
||||
IS_READ_ONLY: true,
|
||||
transformArguments(index: RedisArgument, schema: RediSearchSchema) {
|
||||
const args = ['FT.ALTER', index, 'SCHEMA', 'ADD'];
|
||||
pushSchema(args, schema);
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
export declare function transformReply(): 'OK';
|
||||
},
|
||||
transformReply: undefined as unknown as () => SimpleStringReply<'OK'>
|
||||
} as const satisfies Command;
|
||||
|
@@ -1,25 +1,25 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { transformArguments } from './CONFIG_GET';
|
||||
import CONFIG_GET from './CONFIG_GET';
|
||||
|
||||
describe('CONFIG GET', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('TIMEOUT'),
|
||||
['FT.CONFIG', 'GET', 'TIMEOUT']
|
||||
);
|
||||
});
|
||||
describe('FT.CONFIG GET', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
CONFIG_GET.transformArguments('TIMEOUT'),
|
||||
['FT.CONFIG', 'GET', 'TIMEOUT']
|
||||
);
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.configGet', async client => {
|
||||
assert.deepEqual(
|
||||
await client.ft.configGet('TIMEOUT'),
|
||||
Object.create(null, {
|
||||
TIMEOUT: {
|
||||
value: '500',
|
||||
configurable: true,
|
||||
enumerable: true
|
||||
}
|
||||
})
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
testUtils.testWithClient('client.ft.configGet', async client => {
|
||||
assert.deepEqual(
|
||||
await client.ft.configGet('TIMEOUT'),
|
||||
Object.create(null, {
|
||||
TIMEOUT: {
|
||||
value: '500',
|
||||
configurable: true,
|
||||
enumerable: true
|
||||
}
|
||||
})
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
||||
|
@@ -1,16 +1,18 @@
|
||||
export function transformArguments(option: string) {
|
||||
import { ArrayReply, TuplesReply, BlobStringReply, NullReply, UnwrapReply, Command } from '@redis/client/dist/lib/RESP/types';
|
||||
|
||||
export default {
|
||||
FIRST_KEY_INDEX: undefined,
|
||||
IS_READ_ONLY: true,
|
||||
transformArguments(option: string) {
|
||||
return ['FT.CONFIG', 'GET', option];
|
||||
}
|
||||
|
||||
interface ConfigGetReply {
|
||||
[option: string]: string | null;
|
||||
}
|
||||
|
||||
export function transformReply(rawReply: Array<[string, string | null]>): ConfigGetReply {
|
||||
const transformedReply: ConfigGetReply = Object.create(null);
|
||||
for (const [key, value] of rawReply) {
|
||||
transformedReply[key] = value;
|
||||
},
|
||||
transformReply(reply: UnwrapReply<ArrayReply<TuplesReply<[BlobStringReply, BlobStringReply | NullReply]>>>) {
|
||||
const transformedReply: Record<string, BlobStringReply | NullReply> = Object.create(null);
|
||||
for (const item of reply) {
|
||||
const [key, value] = item as unknown as UnwrapReply<typeof item>;
|
||||
transformedReply[key.toString()] = value;
|
||||
}
|
||||
|
||||
return transformedReply;
|
||||
}
|
||||
}
|
||||
} as const satisfies Command;
|
||||
|
@@ -1,12 +1,19 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { transformArguments } from './CONFIG_SET';
|
||||
import CONFIG_SET from './CONFIG_SET';
|
||||
|
||||
describe('CONFIG SET', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('TIMEOUT', '500'),
|
||||
['FT.CONFIG', 'SET', 'TIMEOUT', '500']
|
||||
);
|
||||
});
|
||||
describe('FT.CONFIG SET', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
CONFIG_SET.transformArguments('TIMEOUT', '500'),
|
||||
['FT.CONFIG', 'SET', 'TIMEOUT', '500']
|
||||
);
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.configSet', async client => {
|
||||
assert.deepEqual(
|
||||
await client.ft.configSet('TIMEOUT', '500'),
|
||||
'OK'
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
||||
|
@@ -1,5 +1,14 @@
|
||||
export function transformArguments(option: string, value: string): Array<string> {
|
||||
return ['FT.CONFIG', 'SET', option, value];
|
||||
}
|
||||
import { RedisArgument, SimpleStringReply, Command } from '@redis/client/dist/lib/RESP/types';
|
||||
|
||||
export declare function transformReply(): 'OK';
|
||||
// using `string & {}` to avoid TS widening the type to `string`
|
||||
// TODO
|
||||
type FtConfigProperties = 'a' | 'b' | (string & {}) | Buffer;
|
||||
|
||||
export default {
|
||||
FIRST_KEY_INDEX: undefined,
|
||||
IS_READ_ONLY: true,
|
||||
transformArguments(property: FtConfigProperties, value: RedisArgument) {
|
||||
return ['FT.CONFIG', 'SET', property, value];
|
||||
},
|
||||
transformReply: undefined as unknown as () => SimpleStringReply<'OK'>
|
||||
} as const satisfies Command;
|
||||
|
@@ -1,490 +1,475 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { transformArguments } from './CREATE';
|
||||
import { SchemaFieldTypes, SchemaTextFieldPhonetics, RedisSearchLanguages, VectorAlgorithms, SCHEMA_GEO_SHAPE_COORD_SYSTEM } from '.';
|
||||
import CREATE, { SCHEMA_FIELD_TYPE, SCHEMA_TEXT_FIELD_PHONETIC, SCHEMA_VECTOR_FIELD_ALGORITHM, REDISEARCH_LANGUAGE } from './CREATE';
|
||||
|
||||
describe('CREATE', () => {
|
||||
describe('transformArguments', () => {
|
||||
it('simple', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {}),
|
||||
['FT.CREATE', 'index', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
describe('with fields', () => {
|
||||
describe('TEXT', () => {
|
||||
it('without options', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {
|
||||
field: SchemaFieldTypes.TEXT
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'TEXT']
|
||||
);
|
||||
});
|
||||
|
||||
it('with NOSTEM', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {
|
||||
field: {
|
||||
type: SchemaFieldTypes.TEXT,
|
||||
NOSTEM: true
|
||||
}
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'TEXT', 'NOSTEM']
|
||||
);
|
||||
});
|
||||
|
||||
it('with WEIGHT', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {
|
||||
field: {
|
||||
type: SchemaFieldTypes.TEXT,
|
||||
WEIGHT: 1
|
||||
}
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'TEXT', 'WEIGHT', '1']
|
||||
);
|
||||
});
|
||||
|
||||
it('with PHONETIC', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {
|
||||
field: {
|
||||
type: SchemaFieldTypes.TEXT,
|
||||
PHONETIC: SchemaTextFieldPhonetics.DM_EN
|
||||
}
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'TEXT', 'PHONETIC', SchemaTextFieldPhonetics.DM_EN]
|
||||
);
|
||||
});
|
||||
|
||||
it('with WITHSUFFIXTRIE', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {
|
||||
field: {
|
||||
type: SchemaFieldTypes.TEXT,
|
||||
WITHSUFFIXTRIE: true
|
||||
}
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'TEXT', 'WITHSUFFIXTRIE']
|
||||
);
|
||||
});
|
||||
|
||||
it('with INDEXEMPTY', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {
|
||||
field: {
|
||||
type: SchemaFieldTypes.TEXT,
|
||||
INDEXEMPTY: true
|
||||
}
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'TEXT', 'INDEXEMPTY']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('NUMERIC', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {
|
||||
field: SchemaFieldTypes.NUMERIC
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'NUMERIC']
|
||||
);
|
||||
});
|
||||
|
||||
it('GEO', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {
|
||||
field: SchemaFieldTypes.GEO
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'GEO']
|
||||
);
|
||||
});
|
||||
|
||||
describe('TAG', () => {
|
||||
describe('without options', () => {
|
||||
it('SchemaFieldTypes.TAG', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {
|
||||
field: SchemaFieldTypes.TAG
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'TAG']
|
||||
);
|
||||
});
|
||||
|
||||
it('{ type: SchemaFieldTypes.TAG }', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {
|
||||
field: {
|
||||
type: SchemaFieldTypes.TAG
|
||||
}
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'TAG']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('with SEPARATOR', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {
|
||||
field: {
|
||||
type: SchemaFieldTypes.TAG,
|
||||
SEPARATOR: 'separator'
|
||||
}
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'TAG', 'SEPARATOR', 'separator']
|
||||
);
|
||||
});
|
||||
|
||||
it('with CASESENSITIVE', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {
|
||||
field: {
|
||||
type: SchemaFieldTypes.TAG,
|
||||
CASESENSITIVE: true
|
||||
}
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'TAG', 'CASESENSITIVE']
|
||||
);
|
||||
});
|
||||
|
||||
it('with WITHSUFFIXTRIE', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {
|
||||
field: {
|
||||
type: SchemaFieldTypes.TAG,
|
||||
WITHSUFFIXTRIE: true
|
||||
}
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'TAG', 'WITHSUFFIXTRIE']
|
||||
);
|
||||
});
|
||||
|
||||
it('with INDEXEMPTY', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {
|
||||
field: {
|
||||
type: SchemaFieldTypes.TAG,
|
||||
INDEXEMPTY: true
|
||||
}
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'TAG', 'INDEXEMPTY']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('VECTOR', () => {
|
||||
it('Flat algorithm', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {
|
||||
field: {
|
||||
type: SchemaFieldTypes.VECTOR,
|
||||
ALGORITHM: VectorAlgorithms.FLAT,
|
||||
TYPE: 'FLOAT32',
|
||||
DIM: 2,
|
||||
DISTANCE_METRIC: 'L2',
|
||||
INITIAL_CAP: 1000000,
|
||||
BLOCK_SIZE: 1000
|
||||
}
|
||||
}),
|
||||
[
|
||||
'FT.CREATE', 'index', 'SCHEMA', 'field', 'VECTOR', 'FLAT', '10', 'TYPE',
|
||||
'FLOAT32', 'DIM', '2', 'DISTANCE_METRIC', 'L2', 'INITIAL_CAP', '1000000',
|
||||
'BLOCK_SIZE', '1000'
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
it('HNSW algorithm', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {
|
||||
field: {
|
||||
type: SchemaFieldTypes.VECTOR,
|
||||
ALGORITHM: VectorAlgorithms.HNSW,
|
||||
TYPE: 'FLOAT32',
|
||||
DIM: 2,
|
||||
DISTANCE_METRIC: 'L2',
|
||||
INITIAL_CAP: 1000000,
|
||||
M: 40,
|
||||
EF_CONSTRUCTION: 250,
|
||||
EF_RUNTIME: 20
|
||||
}
|
||||
}),
|
||||
[
|
||||
'FT.CREATE', 'index', 'SCHEMA', 'field', 'VECTOR', 'HNSW', '14', 'TYPE',
|
||||
'FLOAT32', 'DIM', '2', 'DISTANCE_METRIC', 'L2', 'INITIAL_CAP', '1000000',
|
||||
'M', '40', 'EF_CONSTRUCTION', '250', 'EF_RUNTIME', '20'
|
||||
]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GEOSHAPE', () => {
|
||||
describe('without options', () => {
|
||||
it('SCHEMA_FIELD_TYPE.GEOSHAPE', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {
|
||||
field: SchemaFieldTypes.GEOSHAPE
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'GEOSHAPE']
|
||||
);
|
||||
});
|
||||
|
||||
it('{ type: SCHEMA_FIELD_TYPE.GEOSHAPE }', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {
|
||||
field: {
|
||||
type: SchemaFieldTypes.GEOSHAPE
|
||||
}
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'GEOSHAPE']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('with COORD_SYSTEM', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {
|
||||
field: {
|
||||
type: SchemaFieldTypes.GEOSHAPE,
|
||||
COORD_SYSTEM: SCHEMA_GEO_SHAPE_COORD_SYSTEM.SPHERICAL
|
||||
}
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'GEOSHAPE', 'COORD_SYSTEM', 'SPHERICAL']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with generic options', () => {
|
||||
it('with AS', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {
|
||||
field: {
|
||||
type: SchemaFieldTypes.TEXT,
|
||||
AS: 'as'
|
||||
}
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'AS', 'as', 'TEXT']
|
||||
);
|
||||
});
|
||||
|
||||
describe('with SORTABLE', () => {
|
||||
it('true', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {
|
||||
field: {
|
||||
type: SchemaFieldTypes.TEXT,
|
||||
SORTABLE: true
|
||||
}
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'TEXT', 'SORTABLE']
|
||||
);
|
||||
});
|
||||
|
||||
it('UNF', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {
|
||||
field: {
|
||||
type: SchemaFieldTypes.TEXT,
|
||||
SORTABLE: 'UNF'
|
||||
}
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'TEXT', 'SORTABLE', 'UNF']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('with NOINDEX', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {
|
||||
field: {
|
||||
type: SchemaFieldTypes.TEXT,
|
||||
NOINDEX: true
|
||||
}
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'TEXT', 'NOINDEX']
|
||||
);
|
||||
});
|
||||
|
||||
it('with INDEXMISSING', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {
|
||||
field: {
|
||||
type: SchemaFieldTypes.TEXT,
|
||||
INDEXMISSING: true
|
||||
}
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'TEXT', 'INDEXMISSING']
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('with ON', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {}, {
|
||||
ON: 'HASH'
|
||||
}),
|
||||
['FT.CREATE', 'index', 'ON', 'HASH', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
describe('with PREFIX', () => {
|
||||
it('string', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {}, {
|
||||
PREFIX: 'prefix'
|
||||
}),
|
||||
['FT.CREATE', 'index', 'PREFIX', '1', 'prefix', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
it('Array', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {}, {
|
||||
PREFIX: ['1', '2']
|
||||
}),
|
||||
['FT.CREATE', 'index', 'PREFIX', '2', '1', '2', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('with FILTER', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {}, {
|
||||
FILTER: '@field != ""'
|
||||
}),
|
||||
['FT.CREATE', 'index', 'FILTER', '@field != ""', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
it('with LANGUAGE', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {}, {
|
||||
LANGUAGE: RedisSearchLanguages.ARABIC
|
||||
}),
|
||||
['FT.CREATE', 'index', 'LANGUAGE', RedisSearchLanguages.ARABIC, 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
it('with LANGUAGE_FIELD', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {}, {
|
||||
LANGUAGE_FIELD: '@field'
|
||||
}),
|
||||
['FT.CREATE', 'index', 'LANGUAGE_FIELD', '@field', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
it('with SCORE', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {}, {
|
||||
SCORE: 1
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCORE', '1', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
it('with SCORE_FIELD', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {}, {
|
||||
SCORE_FIELD: '@field'
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCORE_FIELD', '@field', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
it('with MAXTEXTFIELDS', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {}, {
|
||||
MAXTEXTFIELDS: true
|
||||
}),
|
||||
['FT.CREATE', 'index', 'MAXTEXTFIELDS', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
it('with TEMPORARY', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {}, {
|
||||
TEMPORARY: 1
|
||||
}),
|
||||
['FT.CREATE', 'index', 'TEMPORARY', '1', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
it('with NOOFFSETS', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {}, {
|
||||
NOOFFSETS: true
|
||||
}),
|
||||
['FT.CREATE', 'index', 'NOOFFSETS', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
it('with NOHL', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {}, {
|
||||
NOHL: true
|
||||
}),
|
||||
['FT.CREATE', 'index', 'NOHL', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
it('with NOFIELDS', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {}, {
|
||||
NOFIELDS: true
|
||||
}),
|
||||
['FT.CREATE', 'index', 'NOFIELDS', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
it('with NOFREQS', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {}, {
|
||||
NOFREQS: true
|
||||
}),
|
||||
['FT.CREATE', 'index', 'NOFREQS', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
it('with SKIPINITIALSCAN', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {}, {
|
||||
SKIPINITIALSCAN: true
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SKIPINITIALSCAN', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
describe('with STOPWORDS', () => {
|
||||
it('string', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {}, {
|
||||
STOPWORDS: 'stopword'
|
||||
}),
|
||||
['FT.CREATE', 'index', 'STOPWORDS', '1', 'stopword', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
it('Array', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {}, {
|
||||
STOPWORDS: ['1', '2']
|
||||
}),
|
||||
['FT.CREATE', 'index', 'STOPWORDS', '2', '1', '2', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('FT.CREATE', () => {
|
||||
describe('transformArguments', () => {
|
||||
it('simple', () => {
|
||||
assert.deepEqual(
|
||||
CREATE.transformArguments('index', {}),
|
||||
['FT.CREATE', 'index', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.create', async client => {
|
||||
assert.equal(
|
||||
await client.ft.create('index', {
|
||||
field: SchemaFieldTypes.TEXT
|
||||
describe('with fields', () => {
|
||||
describe('TEXT', () => {
|
||||
it('without options', () => {
|
||||
assert.deepEqual(
|
||||
CREATE.transformArguments('index', {
|
||||
field: SCHEMA_FIELD_TYPE.TEXT
|
||||
}),
|
||||
'OK'
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'TEXT']
|
||||
);
|
||||
});
|
||||
|
||||
it('with NOSTEM', () => {
|
||||
assert.deepEqual(
|
||||
CREATE.transformArguments('index', {
|
||||
field: {
|
||||
type: SCHEMA_FIELD_TYPE.TEXT,
|
||||
NOSTEM: true
|
||||
}
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'TEXT', 'NOSTEM']
|
||||
);
|
||||
});
|
||||
|
||||
it('with WEIGHT', () => {
|
||||
assert.deepEqual(
|
||||
CREATE.transformArguments('index', {
|
||||
field: {
|
||||
type: SCHEMA_FIELD_TYPE.TEXT,
|
||||
WEIGHT: 1
|
||||
}
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'TEXT', 'WEIGHT', '1']
|
||||
);
|
||||
});
|
||||
|
||||
it('with PHONETIC', () => {
|
||||
assert.deepEqual(
|
||||
CREATE.transformArguments('index', {
|
||||
field: {
|
||||
type: SCHEMA_FIELD_TYPE.TEXT,
|
||||
PHONETIC: SCHEMA_TEXT_FIELD_PHONETIC.DM_EN
|
||||
}
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'TEXT', 'PHONETIC', SCHEMA_TEXT_FIELD_PHONETIC.DM_EN]
|
||||
);
|
||||
});
|
||||
|
||||
it('with WITHSUFFIXTRIE', () => {
|
||||
assert.deepEqual(
|
||||
CREATE.transformArguments('index', {
|
||||
field: {
|
||||
type: SCHEMA_FIELD_TYPE.TEXT,
|
||||
WITHSUFFIXTRIE: true
|
||||
}
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'TEXT', 'WITHSUFFIXTRIE']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('NUMERIC', () => {
|
||||
assert.deepEqual(
|
||||
CREATE.transformArguments('index', {
|
||||
field: SCHEMA_FIELD_TYPE.NUMERIC
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'NUMERIC']
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
||||
|
||||
it('GEO', () => {
|
||||
assert.deepEqual(
|
||||
CREATE.transformArguments('index', {
|
||||
field: SCHEMA_FIELD_TYPE.GEO
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'GEO']
|
||||
);
|
||||
});
|
||||
|
||||
describe('TAG', () => {
|
||||
describe('without options', () => {
|
||||
it('SCHEMA_FIELD_TYPE.TAG', () => {
|
||||
assert.deepEqual(
|
||||
CREATE.transformArguments('index', {
|
||||
field: SCHEMA_FIELD_TYPE.TAG
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'TAG']
|
||||
);
|
||||
});
|
||||
|
||||
it('{ type: SCHEMA_FIELD_TYPE.TAG }', () => {
|
||||
assert.deepEqual(
|
||||
CREATE.transformArguments('index', {
|
||||
field: {
|
||||
type: SCHEMA_FIELD_TYPE.TAG
|
||||
}
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'TAG']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('with SEPARATOR', () => {
|
||||
assert.deepEqual(
|
||||
CREATE.transformArguments('index', {
|
||||
field: {
|
||||
type: SCHEMA_FIELD_TYPE.TAG,
|
||||
SEPARATOR: 'separator'
|
||||
}
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'TAG', 'SEPARATOR', 'separator']
|
||||
);
|
||||
});
|
||||
|
||||
it('with CASESENSITIVE', () => {
|
||||
assert.deepEqual(
|
||||
CREATE.transformArguments('index', {
|
||||
field: {
|
||||
type: SCHEMA_FIELD_TYPE.TAG,
|
||||
CASESENSITIVE: true
|
||||
}
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'TAG', 'CASESENSITIVE']
|
||||
);
|
||||
});
|
||||
|
||||
it('with WITHSUFFIXTRIE', () => {
|
||||
assert.deepEqual(
|
||||
CREATE.transformArguments('index', {
|
||||
field: {
|
||||
type: SCHEMA_FIELD_TYPE.TAG,
|
||||
WITHSUFFIXTRIE: true
|
||||
}
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'TAG', 'WITHSUFFIXTRIE']
|
||||
);
|
||||
});
|
||||
|
||||
it('with INDEXEMPTY', () => {
|
||||
assert.deepEqual(
|
||||
CREATE.transformArguments('index', {
|
||||
field: {
|
||||
type: SCHEMA_FIELD_TYPE.TAG,
|
||||
INDEXEMPTY: true
|
||||
}
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'TAG', 'INDEXEMPTY']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('VECTOR', () => {
|
||||
it('Flat algorithm', () => {
|
||||
assert.deepEqual(
|
||||
CREATE.transformArguments('index', {
|
||||
field: {
|
||||
type: SCHEMA_FIELD_TYPE.VECTOR,
|
||||
ALGORITHM: SCHEMA_VECTOR_FIELD_ALGORITHM.FLAT,
|
||||
TYPE: 'FLOAT32',
|
||||
DIM: 2,
|
||||
DISTANCE_METRIC: 'L2',
|
||||
INITIAL_CAP: 1000000,
|
||||
BLOCK_SIZE: 1000
|
||||
}
|
||||
}),
|
||||
[
|
||||
'FT.CREATE', 'index', 'SCHEMA', 'field', 'VECTOR', 'FLAT', '10', 'TYPE',
|
||||
'FLOAT32', 'DIM', '2', 'DISTANCE_METRIC', 'L2', 'INITIAL_CAP', '1000000',
|
||||
'BLOCK_SIZE', '1000'
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
it('HNSW algorithm', () => {
|
||||
assert.deepEqual(
|
||||
CREATE.transformArguments('index', {
|
||||
field: {
|
||||
type: SCHEMA_FIELD_TYPE.VECTOR,
|
||||
ALGORITHM: SCHEMA_VECTOR_FIELD_ALGORITHM.HNSW,
|
||||
TYPE: 'FLOAT32',
|
||||
DIM: 2,
|
||||
DISTANCE_METRIC: 'L2',
|
||||
INITIAL_CAP: 1000000,
|
||||
M: 40,
|
||||
EF_CONSTRUCTION: 250,
|
||||
EF_RUNTIME: 20
|
||||
}
|
||||
}),
|
||||
[
|
||||
'FT.CREATE', 'index', 'SCHEMA', 'field', 'VECTOR', 'HNSW', '14', 'TYPE',
|
||||
'FLOAT32', 'DIM', '2', 'DISTANCE_METRIC', 'L2', 'INITIAL_CAP', '1000000',
|
||||
'M', '40', 'EF_CONSTRUCTION', '250', 'EF_RUNTIME', '20'
|
||||
]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GEOSHAPE', () => {
|
||||
describe('without options', () => {
|
||||
it('SCHEMA_FIELD_TYPE.GEOSHAPE', () => {
|
||||
assert.deepEqual(
|
||||
CREATE.transformArguments('index', {
|
||||
field: SCHEMA_FIELD_TYPE.GEOSHAPE
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'GEOSHAPE']
|
||||
);
|
||||
});
|
||||
|
||||
it('{ type: SCHEMA_FIELD_TYPE.GEOSHAPE }', () => {
|
||||
assert.deepEqual(
|
||||
CREATE.transformArguments('index', {
|
||||
field: {
|
||||
type: SCHEMA_FIELD_TYPE.GEOSHAPE
|
||||
}
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'GEOSHAPE']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('with COORD_SYSTEM', () => {
|
||||
assert.deepEqual(
|
||||
CREATE.transformArguments('index', {
|
||||
field: {
|
||||
type: SCHEMA_FIELD_TYPE.GEOSHAPE,
|
||||
COORD_SYSTEM: 'SPHERICAL'
|
||||
}
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'GEOSHAPE', 'COORD_SYSTEM', 'SPHERICAL']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('with AS', () => {
|
||||
assert.deepEqual(
|
||||
CREATE.transformArguments('index', {
|
||||
field: {
|
||||
type: SCHEMA_FIELD_TYPE.TEXT,
|
||||
AS: 'as'
|
||||
}
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'AS', 'as', 'TEXT']
|
||||
);
|
||||
});
|
||||
|
||||
describe('with SORTABLE', () => {
|
||||
it('true', () => {
|
||||
assert.deepEqual(
|
||||
CREATE.transformArguments('index', {
|
||||
field: {
|
||||
type: SCHEMA_FIELD_TYPE.TEXT,
|
||||
SORTABLE: true
|
||||
}
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'TEXT', 'SORTABLE']
|
||||
);
|
||||
});
|
||||
|
||||
it('UNF', () => {
|
||||
assert.deepEqual(
|
||||
CREATE.transformArguments('index', {
|
||||
field: {
|
||||
type: SCHEMA_FIELD_TYPE.TEXT,
|
||||
SORTABLE: 'UNF'
|
||||
}
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'TEXT', 'SORTABLE', 'UNF']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('with NOINDEX', () => {
|
||||
assert.deepEqual(
|
||||
CREATE.transformArguments('index', {
|
||||
field: {
|
||||
type: SCHEMA_FIELD_TYPE.TEXT,
|
||||
NOINDEX: true
|
||||
}
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'TEXT', 'NOINDEX']
|
||||
);
|
||||
});
|
||||
|
||||
it('with INDEXMISSING', () => {
|
||||
assert.deepEqual(
|
||||
CREATE.transformArguments('index', {
|
||||
field: {
|
||||
type: SCHEMA_FIELD_TYPE.TEXT,
|
||||
INDEXMISSING: true
|
||||
}
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'TEXT', 'INDEXMISSING']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('with ON', () => {
|
||||
assert.deepEqual(
|
||||
CREATE.transformArguments('index', {}, {
|
||||
ON: 'HASH'
|
||||
}),
|
||||
['FT.CREATE', 'index', 'ON', 'HASH', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
describe('with PREFIX', () => {
|
||||
it('string', () => {
|
||||
assert.deepEqual(
|
||||
CREATE.transformArguments('index', {}, {
|
||||
PREFIX: 'prefix'
|
||||
}),
|
||||
['FT.CREATE', 'index', 'PREFIX', '1', 'prefix', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
it('Array', () => {
|
||||
assert.deepEqual(
|
||||
CREATE.transformArguments('index', {}, {
|
||||
PREFIX: ['1', '2']
|
||||
}),
|
||||
['FT.CREATE', 'index', 'PREFIX', '2', '1', '2', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('with FILTER', () => {
|
||||
assert.deepEqual(
|
||||
CREATE.transformArguments('index', {}, {
|
||||
FILTER: '@field != ""'
|
||||
}),
|
||||
['FT.CREATE', 'index', 'FILTER', '@field != ""', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
it('with LANGUAGE', () => {
|
||||
assert.deepEqual(
|
||||
CREATE.transformArguments('index', {}, {
|
||||
LANGUAGE: REDISEARCH_LANGUAGE.ARABIC
|
||||
}),
|
||||
['FT.CREATE', 'index', 'LANGUAGE', REDISEARCH_LANGUAGE.ARABIC, 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
it('with LANGUAGE_FIELD', () => {
|
||||
assert.deepEqual(
|
||||
CREATE.transformArguments('index', {}, {
|
||||
LANGUAGE_FIELD: '@field'
|
||||
}),
|
||||
['FT.CREATE', 'index', 'LANGUAGE_FIELD', '@field', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
it('with SCORE', () => {
|
||||
assert.deepEqual(
|
||||
CREATE.transformArguments('index', {}, {
|
||||
SCORE: 1
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCORE', '1', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
it('with SCORE_FIELD', () => {
|
||||
assert.deepEqual(
|
||||
CREATE.transformArguments('index', {}, {
|
||||
SCORE_FIELD: '@field'
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCORE_FIELD', '@field', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
it('with MAXTEXTFIELDS', () => {
|
||||
assert.deepEqual(
|
||||
CREATE.transformArguments('index', {}, {
|
||||
MAXTEXTFIELDS: true
|
||||
}),
|
||||
['FT.CREATE', 'index', 'MAXTEXTFIELDS', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
it('with TEMPORARY', () => {
|
||||
assert.deepEqual(
|
||||
CREATE.transformArguments('index', {}, {
|
||||
TEMPORARY: 1
|
||||
}),
|
||||
['FT.CREATE', 'index', 'TEMPORARY', '1', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
it('with NOOFFSETS', () => {
|
||||
assert.deepEqual(
|
||||
CREATE.transformArguments('index', {}, {
|
||||
NOOFFSETS: true
|
||||
}),
|
||||
['FT.CREATE', 'index', 'NOOFFSETS', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
it('with NOHL', () => {
|
||||
assert.deepEqual(
|
||||
CREATE.transformArguments('index', {}, {
|
||||
NOHL: true
|
||||
}),
|
||||
['FT.CREATE', 'index', 'NOHL', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
it('with NOFIELDS', () => {
|
||||
assert.deepEqual(
|
||||
CREATE.transformArguments('index', {}, {
|
||||
NOFIELDS: true
|
||||
}),
|
||||
['FT.CREATE', 'index', 'NOFIELDS', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
it('with NOFREQS', () => {
|
||||
assert.deepEqual(
|
||||
CREATE.transformArguments('index', {}, {
|
||||
NOFREQS: true
|
||||
}),
|
||||
['FT.CREATE', 'index', 'NOFREQS', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
it('with SKIPINITIALSCAN', () => {
|
||||
assert.deepEqual(
|
||||
CREATE.transformArguments('index', {}, {
|
||||
SKIPINITIALSCAN: true
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SKIPINITIALSCAN', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
describe('with STOPWORDS', () => {
|
||||
it('string', () => {
|
||||
assert.deepEqual(
|
||||
CREATE.transformArguments('index', {}, {
|
||||
STOPWORDS: 'stopword'
|
||||
}),
|
||||
['FT.CREATE', 'index', 'STOPWORDS', '1', 'stopword', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
it('Array', () => {
|
||||
assert.deepEqual(
|
||||
CREATE.transformArguments('index', {}, {
|
||||
STOPWORDS: ['1', '2']
|
||||
}),
|
||||
['FT.CREATE', 'index', 'STOPWORDS', '2', '1', '2', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.create', async client => {
|
||||
assert.equal(
|
||||
await client.ft.create('index', {
|
||||
field: SCHEMA_FIELD_TYPE.TEXT
|
||||
}),
|
||||
'OK'
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
||||
|
@@ -1,52 +1,323 @@
|
||||
import { pushOptionalVerdictArgument } from '@redis/client/dist/lib/commands/generic-transformers';
|
||||
import { RedisSearchLanguages, PropertyName, RediSearchSchema, pushSchema } from '.';
|
||||
import { RedisArgument, SimpleStringReply, Command, CommandArguments } from '@redis/client/lib/RESP/types';
|
||||
import { RedisVariadicArgument, pushOptionalVariadicArgument } from '@redis/client/lib/commands/generic-transformers';
|
||||
|
||||
interface CreateOptions {
|
||||
ON?: 'HASH' | 'JSON';
|
||||
PREFIX?: string | Array<string>;
|
||||
FILTER?: string;
|
||||
LANGUAGE?: RedisSearchLanguages;
|
||||
LANGUAGE_FIELD?: PropertyName;
|
||||
SCORE?: number;
|
||||
SCORE_FIELD?: PropertyName;
|
||||
// PAYLOAD_FIELD?: string;
|
||||
MAXTEXTFIELDS?: true;
|
||||
TEMPORARY?: number;
|
||||
NOOFFSETS?: true;
|
||||
NOHL?: true;
|
||||
NOFIELDS?: true;
|
||||
NOFREQS?: true;
|
||||
SKIPINITIALSCAN?: true;
|
||||
STOPWORDS?: string | Array<string>;
|
||||
export const SCHEMA_FIELD_TYPE = {
|
||||
TEXT: 'TEXT',
|
||||
NUMERIC: 'NUMERIC',
|
||||
GEO: 'GEO',
|
||||
TAG: 'TAG',
|
||||
VECTOR: 'VECTOR',
|
||||
GEOSHAPE: 'GEOSHAPE'
|
||||
} as const;
|
||||
|
||||
export type SchemaFieldType = typeof SCHEMA_FIELD_TYPE[keyof typeof SCHEMA_FIELD_TYPE];
|
||||
|
||||
interface SchemaField<T extends SchemaFieldType = SchemaFieldType> {
|
||||
type: T;
|
||||
AS?: RedisArgument;
|
||||
INDEXMISSING?: boolean;
|
||||
}
|
||||
|
||||
export function transformArguments(index: string, schema: RediSearchSchema, options?: CreateOptions): Array<string> {
|
||||
interface SchemaCommonField<T extends SchemaFieldType = SchemaFieldType> extends SchemaField<T> {
|
||||
SORTABLE?: boolean | 'UNF'
|
||||
NOINDEX?: boolean;
|
||||
}
|
||||
|
||||
export const SCHEMA_TEXT_FIELD_PHONETIC = {
|
||||
DM_EN: 'dm:en',
|
||||
DM_FR: 'dm:fr',
|
||||
FM_PT: 'dm:pt',
|
||||
DM_ES: 'dm:es'
|
||||
} as const;
|
||||
|
||||
export type SchemaTextFieldPhonetic = typeof SCHEMA_TEXT_FIELD_PHONETIC[keyof typeof SCHEMA_TEXT_FIELD_PHONETIC];
|
||||
|
||||
interface SchemaTextField extends SchemaCommonField<typeof SCHEMA_FIELD_TYPE['TEXT']> {
|
||||
NOSTEM?: boolean;
|
||||
WEIGHT?: number;
|
||||
PHONETIC?: SchemaTextFieldPhonetic;
|
||||
WITHSUFFIXTRIE?: boolean;
|
||||
INDEXEMPTY?: boolean;
|
||||
}
|
||||
|
||||
interface SchemaNumericField extends SchemaCommonField<typeof SCHEMA_FIELD_TYPE['NUMERIC']> {}
|
||||
|
||||
interface SchemaGeoField extends SchemaCommonField<typeof SCHEMA_FIELD_TYPE['GEO']> {}
|
||||
|
||||
interface SchemaTagField extends SchemaCommonField<typeof SCHEMA_FIELD_TYPE['TAG']> {
|
||||
SEPARATOR?: RedisArgument;
|
||||
CASESENSITIVE?: boolean;
|
||||
WITHSUFFIXTRIE?: boolean;
|
||||
INDEXEMPTY?: boolean;
|
||||
}
|
||||
|
||||
export const SCHEMA_VECTOR_FIELD_ALGORITHM = {
|
||||
FLAT: 'FLAT',
|
||||
HNSW: 'HNSW'
|
||||
} as const;
|
||||
|
||||
export type SchemaVectorFieldAlgorithm = typeof SCHEMA_VECTOR_FIELD_ALGORITHM[keyof typeof SCHEMA_VECTOR_FIELD_ALGORITHM];
|
||||
|
||||
interface SchemaVectorField extends SchemaField<typeof SCHEMA_FIELD_TYPE['VECTOR']> {
|
||||
ALGORITHM: SchemaVectorFieldAlgorithm;
|
||||
TYPE: string;
|
||||
DIM: number;
|
||||
DISTANCE_METRIC: 'L2' | 'IP' | 'COSINE';
|
||||
INITIAL_CAP?: number;
|
||||
}
|
||||
|
||||
interface SchemaFlatVectorField extends SchemaVectorField {
|
||||
ALGORITHM: typeof SCHEMA_VECTOR_FIELD_ALGORITHM['FLAT'];
|
||||
BLOCK_SIZE?: number;
|
||||
}
|
||||
|
||||
interface SchemaHNSWVectorField extends SchemaVectorField {
|
||||
ALGORITHM: typeof SCHEMA_VECTOR_FIELD_ALGORITHM['HNSW'];
|
||||
M?: number;
|
||||
EF_CONSTRUCTION?: number;
|
||||
EF_RUNTIME?: number;
|
||||
}
|
||||
|
||||
export const SCHEMA_GEO_SHAPE_COORD_SYSTEM = {
|
||||
SPHERICAL: 'SPHERICAL',
|
||||
FLAT: 'FLAT'
|
||||
} as const;
|
||||
|
||||
export type SchemaGeoShapeFieldCoordSystem = typeof SCHEMA_GEO_SHAPE_COORD_SYSTEM[keyof typeof SCHEMA_GEO_SHAPE_COORD_SYSTEM];
|
||||
|
||||
interface SchemaGeoShapeField extends SchemaField<typeof SCHEMA_FIELD_TYPE['GEOSHAPE']> {
|
||||
COORD_SYSTEM?: SchemaGeoShapeFieldCoordSystem;
|
||||
}
|
||||
|
||||
export interface RediSearchSchema {
|
||||
[field: string]: (
|
||||
SchemaTextField |
|
||||
SchemaNumericField |
|
||||
SchemaGeoField |
|
||||
SchemaTagField |
|
||||
SchemaFlatVectorField |
|
||||
SchemaHNSWVectorField |
|
||||
SchemaGeoShapeField |
|
||||
SchemaFieldType
|
||||
);
|
||||
}
|
||||
|
||||
function pushCommonSchemaFieldOptions(args: CommandArguments, fieldOptions: SchemaCommonField) {
|
||||
if (fieldOptions.SORTABLE) {
|
||||
args.push('SORTABLE');
|
||||
|
||||
if (fieldOptions.SORTABLE === 'UNF') {
|
||||
args.push('UNF');
|
||||
}
|
||||
}
|
||||
|
||||
if (fieldOptions.NOINDEX) {
|
||||
args.push('NOINDEX');
|
||||
}
|
||||
}
|
||||
|
||||
export function pushSchema(args: CommandArguments, schema: RediSearchSchema) {
|
||||
for (const [field, fieldOptions] of Object.entries(schema)) {
|
||||
args.push(field);
|
||||
|
||||
if (typeof fieldOptions === 'string') {
|
||||
args.push(fieldOptions);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fieldOptions.AS) {
|
||||
args.push('AS', fieldOptions.AS);
|
||||
}
|
||||
|
||||
args.push(fieldOptions.type);
|
||||
|
||||
if (fieldOptions.INDEXMISSING) {
|
||||
args.push('INDEXMISSING');
|
||||
}
|
||||
|
||||
switch (fieldOptions.type) {
|
||||
case SCHEMA_FIELD_TYPE.TEXT:
|
||||
if (fieldOptions.NOSTEM) {
|
||||
args.push('NOSTEM');
|
||||
}
|
||||
|
||||
if (fieldOptions.WEIGHT) {
|
||||
args.push('WEIGHT', fieldOptions.WEIGHT.toString());
|
||||
}
|
||||
|
||||
if (fieldOptions.PHONETIC) {
|
||||
args.push('PHONETIC', fieldOptions.PHONETIC);
|
||||
}
|
||||
|
||||
if (fieldOptions.WITHSUFFIXTRIE) {
|
||||
args.push('WITHSUFFIXTRIE');
|
||||
}
|
||||
|
||||
if (fieldOptions.INDEXEMPTY) {
|
||||
args.push('INDEXEMPTY');
|
||||
}
|
||||
|
||||
pushCommonSchemaFieldOptions(args, fieldOptions)
|
||||
break;
|
||||
|
||||
case SCHEMA_FIELD_TYPE.NUMERIC:
|
||||
case SCHEMA_FIELD_TYPE.GEO:
|
||||
pushCommonSchemaFieldOptions(args, fieldOptions)
|
||||
break;
|
||||
|
||||
case SCHEMA_FIELD_TYPE.TAG:
|
||||
if (fieldOptions.SEPARATOR) {
|
||||
args.push('SEPARATOR', fieldOptions.SEPARATOR);
|
||||
}
|
||||
|
||||
if (fieldOptions.CASESENSITIVE) {
|
||||
args.push('CASESENSITIVE');
|
||||
}
|
||||
|
||||
if (fieldOptions.WITHSUFFIXTRIE) {
|
||||
args.push('WITHSUFFIXTRIE');
|
||||
}
|
||||
|
||||
if (fieldOptions.INDEXEMPTY) {
|
||||
args.push('INDEXEMPTY');
|
||||
}
|
||||
|
||||
pushCommonSchemaFieldOptions(args, fieldOptions)
|
||||
break;
|
||||
|
||||
case SCHEMA_FIELD_TYPE.VECTOR:
|
||||
args.push(fieldOptions.ALGORITHM);
|
||||
|
||||
const lengthIndex = args.push('') - 1;
|
||||
|
||||
args.push(
|
||||
'TYPE', fieldOptions.TYPE,
|
||||
'DIM', fieldOptions.DIM.toString(),
|
||||
'DISTANCE_METRIC', fieldOptions.DISTANCE_METRIC
|
||||
);
|
||||
|
||||
if (fieldOptions.INITIAL_CAP) {
|
||||
args.push('INITIAL_CAP', fieldOptions.INITIAL_CAP.toString());
|
||||
}
|
||||
|
||||
switch (fieldOptions.ALGORITHM) {
|
||||
case SCHEMA_VECTOR_FIELD_ALGORITHM.FLAT:
|
||||
if (fieldOptions.BLOCK_SIZE) {
|
||||
args.push('BLOCK_SIZE', fieldOptions.BLOCK_SIZE.toString());
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case SCHEMA_VECTOR_FIELD_ALGORITHM.HNSW:
|
||||
if (fieldOptions.M) {
|
||||
args.push('M', fieldOptions.M.toString());
|
||||
}
|
||||
|
||||
if (fieldOptions.EF_CONSTRUCTION) {
|
||||
args.push('EF_CONSTRUCTION', fieldOptions.EF_CONSTRUCTION.toString());
|
||||
}
|
||||
|
||||
if (fieldOptions.EF_RUNTIME) {
|
||||
args.push('EF_RUNTIME', fieldOptions.EF_RUNTIME.toString());
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
args[lengthIndex] = (args.length - lengthIndex - 1).toString();
|
||||
|
||||
break;
|
||||
|
||||
case SCHEMA_FIELD_TYPE.GEOSHAPE:
|
||||
if (fieldOptions.COORD_SYSTEM !== undefined) {
|
||||
args.push('COORD_SYSTEM', fieldOptions.COORD_SYSTEM);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const REDISEARCH_LANGUAGE = {
|
||||
ARABIC: 'Arabic',
|
||||
BASQUE: 'Basque',
|
||||
CATALANA: 'Catalan',
|
||||
DANISH: 'Danish',
|
||||
DUTCH: 'Dutch',
|
||||
ENGLISH: 'English',
|
||||
FINNISH: 'Finnish',
|
||||
FRENCH: 'French',
|
||||
GERMAN: 'German',
|
||||
GREEK: 'Greek',
|
||||
HUNGARIAN: 'Hungarian',
|
||||
INDONESAIN: 'Indonesian',
|
||||
IRISH: 'Irish',
|
||||
ITALIAN: 'Italian',
|
||||
LITHUANIAN: 'Lithuanian',
|
||||
NEPALI: 'Nepali',
|
||||
NORWEIGAN: 'Norwegian',
|
||||
PORTUGUESE: 'Portuguese',
|
||||
ROMANIAN: 'Romanian',
|
||||
RUSSIAN: 'Russian',
|
||||
SPANISH: 'Spanish',
|
||||
SWEDISH: 'Swedish',
|
||||
TAMIL: 'Tamil',
|
||||
TURKISH: 'Turkish',
|
||||
CHINESE: 'Chinese'
|
||||
} as const;
|
||||
|
||||
export type RediSearchLanguage = typeof REDISEARCH_LANGUAGE[keyof typeof REDISEARCH_LANGUAGE];
|
||||
|
||||
export type RediSearchProperty = `${'@' | '$.'}${string}`;
|
||||
|
||||
export interface CreateOptions {
|
||||
ON?: 'HASH' | 'JSON';
|
||||
PREFIX?: RedisVariadicArgument;
|
||||
FILTER?: RedisArgument;
|
||||
LANGUAGE?: RediSearchLanguage;
|
||||
LANGUAGE_FIELD?: RediSearchProperty;
|
||||
SCORE?: number;
|
||||
SCORE_FIELD?: RediSearchProperty;
|
||||
// PAYLOAD_FIELD?: string;
|
||||
MAXTEXTFIELDS?: boolean;
|
||||
TEMPORARY?: number;
|
||||
NOOFFSETS?: boolean;
|
||||
NOHL?: boolean;
|
||||
NOFIELDS?: boolean;
|
||||
NOFREQS?: boolean;
|
||||
SKIPINITIALSCAN?: boolean;
|
||||
STOPWORDS?: RedisVariadicArgument;
|
||||
}
|
||||
|
||||
export default {
|
||||
FIRST_KEY_INDEX: undefined,
|
||||
IS_READ_ONLY: true,
|
||||
transformArguments(index: RedisArgument, schema: RediSearchSchema, options?: CreateOptions) {
|
||||
const args = ['FT.CREATE', index];
|
||||
|
||||
if (options?.ON) {
|
||||
args.push('ON', options.ON);
|
||||
args.push('ON', options.ON);
|
||||
}
|
||||
|
||||
pushOptionalVerdictArgument(args, 'PREFIX', options?.PREFIX);
|
||||
pushOptionalVariadicArgument(args, 'PREFIX', options?.PREFIX);
|
||||
|
||||
if (options?.FILTER) {
|
||||
args.push('FILTER', options.FILTER);
|
||||
args.push('FILTER', options.FILTER);
|
||||
}
|
||||
|
||||
if (options?.LANGUAGE) {
|
||||
args.push('LANGUAGE', options.LANGUAGE);
|
||||
args.push('LANGUAGE', options.LANGUAGE);
|
||||
}
|
||||
|
||||
if (options?.LANGUAGE_FIELD) {
|
||||
args.push('LANGUAGE_FIELD', options.LANGUAGE_FIELD);
|
||||
args.push('LANGUAGE_FIELD', options.LANGUAGE_FIELD);
|
||||
}
|
||||
|
||||
if (options?.SCORE) {
|
||||
args.push('SCORE', options.SCORE.toString());
|
||||
args.push('SCORE', options.SCORE.toString());
|
||||
}
|
||||
|
||||
if (options?.SCORE_FIELD) {
|
||||
args.push('SCORE_FIELD', options.SCORE_FIELD);
|
||||
args.push('SCORE_FIELD', options.SCORE_FIELD);
|
||||
}
|
||||
|
||||
// if (options?.PAYLOAD_FIELD) {
|
||||
@@ -54,38 +325,38 @@ export function transformArguments(index: string, schema: RediSearchSchema, opti
|
||||
// }
|
||||
|
||||
if (options?.MAXTEXTFIELDS) {
|
||||
args.push('MAXTEXTFIELDS');
|
||||
args.push('MAXTEXTFIELDS');
|
||||
}
|
||||
|
||||
if (options?.TEMPORARY) {
|
||||
args.push('TEMPORARY', options.TEMPORARY.toString());
|
||||
args.push('TEMPORARY', options.TEMPORARY.toString());
|
||||
}
|
||||
|
||||
if (options?.NOOFFSETS) {
|
||||
args.push('NOOFFSETS');
|
||||
args.push('NOOFFSETS');
|
||||
}
|
||||
|
||||
if (options?.NOHL) {
|
||||
args.push('NOHL');
|
||||
args.push('NOHL');
|
||||
}
|
||||
|
||||
if (options?.NOFIELDS) {
|
||||
args.push('NOFIELDS');
|
||||
args.push('NOFIELDS');
|
||||
}
|
||||
|
||||
if (options?.NOFREQS) {
|
||||
args.push('NOFREQS');
|
||||
args.push('NOFREQS');
|
||||
}
|
||||
|
||||
if (options?.SKIPINITIALSCAN) {
|
||||
args.push('SKIPINITIALSCAN');
|
||||
args.push('SKIPINITIALSCAN');
|
||||
}
|
||||
|
||||
pushOptionalVerdictArgument(args, 'STOPWORDS', options?.STOPWORDS);
|
||||
pushOptionalVariadicArgument(args, 'STOPWORDS', options?.STOPWORDS);
|
||||
args.push('SCHEMA');
|
||||
pushSchema(args, schema);
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
export declare function transformReply(): 'OK';
|
||||
},
|
||||
transformReply: undefined as unknown as () => SimpleStringReply<'OK'>
|
||||
} as const satisfies Command;
|
||||
|
@@ -1,33 +1,32 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import { SchemaFieldTypes } from '.';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { transformArguments } from './CURSOR_DEL';
|
||||
import CURSOR_DEL from './CURSOR_DEL';
|
||||
import { SCHEMA_FIELD_TYPE } from './CREATE';
|
||||
|
||||
describe('CURSOR DEL', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 0),
|
||||
['FT.CURSOR', 'DEL', 'index', '0']
|
||||
);
|
||||
});
|
||||
describe('FT.CURSOR DEL', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
CURSOR_DEL.transformArguments('index', 0),
|
||||
['FT.CURSOR', 'DEL', 'index', '0']
|
||||
);
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.cursorDel', async client => {
|
||||
const [ ,, { cursor } ] = await Promise.all([
|
||||
client.ft.create('idx', {
|
||||
field: {
|
||||
type: SchemaFieldTypes.TEXT
|
||||
}
|
||||
}),
|
||||
client.hSet('key', 'field', 'value'),
|
||||
client.ft.aggregateWithCursor('idx', '*', {
|
||||
COUNT: 1
|
||||
})
|
||||
]);
|
||||
testUtils.testWithClient('client.ft.cursorDel', async client => {
|
||||
const [, , { cursor }] = await Promise.all([
|
||||
client.ft.create('idx', {
|
||||
field: {
|
||||
type: SCHEMA_FIELD_TYPE.TEXT
|
||||
}
|
||||
}),
|
||||
client.hSet('key', 'field', 'value'),
|
||||
client.ft.aggregateWithCursor('idx', '*', {
|
||||
COUNT: 1
|
||||
})
|
||||
]);
|
||||
|
||||
|
||||
assert.equal(
|
||||
await client.ft.cursorDel('idx', cursor),
|
||||
'OK'
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
assert.equal(
|
||||
await client.ft.cursorDel('idx', cursor),
|
||||
'OK'
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
||||
|
@@ -1,14 +1,10 @@
|
||||
import { RedisCommandArgument } from '@redis/client/dist/lib/commands';
|
||||
import { SimpleStringReply, Command, RedisArgument, NumberReply, UnwrapReply } from '@redis/client/dist/lib/RESP/types';
|
||||
|
||||
export const FIRST_KEY_INDEX = 1;
|
||||
|
||||
export function transformArguments(index: RedisCommandArgument, cursorId: number) {
|
||||
return [
|
||||
'FT.CURSOR',
|
||||
'DEL',
|
||||
index,
|
||||
cursorId.toString()
|
||||
];
|
||||
}
|
||||
|
||||
export declare function transformReply(): 'OK';
|
||||
export default {
|
||||
FIRST_KEY_INDEX: undefined,
|
||||
IS_READ_ONLY: true,
|
||||
transformArguments(index: RedisArgument, cursorId: UnwrapReply<NumberReply>) {
|
||||
return ['FT.CURSOR', 'DEL', index, cursorId.toString()];
|
||||
},
|
||||
transformReply: undefined as unknown as () => SimpleStringReply<'OK'>
|
||||
} as const satisfies Command;
|
||||
|
@@ -1,45 +1,44 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import { SchemaFieldTypes } from '.';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { transformArguments } from './CURSOR_READ';
|
||||
import CURSOR_READ from './CURSOR_READ';
|
||||
|
||||
describe('CURSOR READ', () => {
|
||||
describe('transformArguments', () => {
|
||||
it('without options', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 0),
|
||||
['FT.CURSOR', 'READ', 'index', '0']
|
||||
);
|
||||
});
|
||||
|
||||
it('with COUNT', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 0, { COUNT: 1 }),
|
||||
['FT.CURSOR', 'READ', 'index', '0', 'COUNT', '1']
|
||||
);
|
||||
});
|
||||
describe('FT.CURSOR READ', () => {
|
||||
describe('transformArguments', () => {
|
||||
it('without options', () => {
|
||||
assert.deepEqual(
|
||||
CURSOR_READ.transformArguments('index', 0),
|
||||
['FT.CURSOR', 'READ', 'index', '0']
|
||||
);
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.cursorRead', async client => {
|
||||
const [, , { cursor }] = await Promise.all([
|
||||
client.ft.create('idx', {
|
||||
field: {
|
||||
type: SchemaFieldTypes.TEXT
|
||||
}
|
||||
}),
|
||||
client.hSet('key', 'field', 'value'),
|
||||
client.ft.aggregateWithCursor('idx', '*', {
|
||||
COUNT: 1
|
||||
})
|
||||
]);
|
||||
it('with COUNT', () => {
|
||||
assert.deepEqual(
|
||||
CURSOR_READ.transformArguments('index', 0, {
|
||||
COUNT: 1
|
||||
}),
|
||||
['FT.CURSOR', 'READ', 'index', '0', 'COUNT', '1']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
await client.ft.cursorRead('idx', cursor),
|
||||
{
|
||||
total: 0,
|
||||
results: [],
|
||||
cursor: 0
|
||||
}
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
testUtils.testWithClient('client.ft.cursorRead', async client => {
|
||||
const [, , { cursor }] = await Promise.all([
|
||||
client.ft.create('idx', {
|
||||
field: 'TEXT'
|
||||
}),
|
||||
client.hSet('key', 'field', 'value'),
|
||||
client.ft.aggregateWithCursor('idx', '*', {
|
||||
COUNT: 1
|
||||
})
|
||||
]);
|
||||
|
||||
assert.deepEqual(
|
||||
await client.ft.cursorRead('idx', cursor),
|
||||
{
|
||||
total: 0,
|
||||
results: [],
|
||||
cursor: 0
|
||||
}
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
||||
|
@@ -1,30 +1,22 @@
|
||||
import { RedisCommandArgument, RedisCommandArguments } from '@redis/client/dist/lib/commands';
|
||||
import { RedisArgument, Command, UnwrapReply, NumberReply } from '@redis/client/dist/lib/RESP/types';
|
||||
import AGGREGATE_WITHCURSOR from './AGGREGATE_WITHCURSOR';
|
||||
|
||||
export const FIRST_KEY_INDEX = 1;
|
||||
|
||||
export const IS_READ_ONLY = true;
|
||||
|
||||
interface CursorReadOptions {
|
||||
COUNT?: number;
|
||||
export interface FtCursorReadOptions {
|
||||
COUNT?: number;
|
||||
}
|
||||
|
||||
export function transformArguments(
|
||||
index: RedisCommandArgument,
|
||||
cursor: number,
|
||||
options?: CursorReadOptions
|
||||
): RedisCommandArguments {
|
||||
const args = [
|
||||
'FT.CURSOR',
|
||||
'READ',
|
||||
index,
|
||||
cursor.toString()
|
||||
];
|
||||
export default {
|
||||
FIRST_KEY_INDEX: undefined,
|
||||
IS_READ_ONLY: true,
|
||||
transformArguments(index: RedisArgument, cursor: UnwrapReply<NumberReply>, options?: FtCursorReadOptions) {
|
||||
const args = ['FT.CURSOR', 'READ', index, cursor.toString()];
|
||||
|
||||
if (options?.COUNT) {
|
||||
args.push('COUNT', options.COUNT.toString());
|
||||
if (options?.COUNT !== undefined) {
|
||||
args.push('COUNT', options.COUNT.toString());
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
export { transformReply } from './AGGREGATE_WITHCURSOR';
|
||||
},
|
||||
transformReply: AGGREGATE_WITHCURSOR.transformReply,
|
||||
unstableResp3: true
|
||||
} as const satisfies Command;
|
||||
|
@@ -1,28 +1,28 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { transformArguments } from './DICTADD';
|
||||
import DICTADD from './DICTADD';
|
||||
|
||||
describe('DICTADD', () => {
|
||||
describe('transformArguments', () => {
|
||||
it('string', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('dictionary', 'term'),
|
||||
['FT.DICTADD', 'dictionary', 'term']
|
||||
);
|
||||
});
|
||||
|
||||
it('Array', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('dictionary', ['1', '2']),
|
||||
['FT.DICTADD', 'dictionary', '1', '2']
|
||||
);
|
||||
});
|
||||
describe('FT.DICTADD', () => {
|
||||
describe('transformArguments', () => {
|
||||
it('string', () => {
|
||||
assert.deepEqual(
|
||||
DICTADD.transformArguments('dictionary', 'term'),
|
||||
['FT.DICTADD', 'dictionary', 'term']
|
||||
);
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.dictAdd', async client => {
|
||||
assert.equal(
|
||||
await client.ft.dictAdd('dictionary', 'term'),
|
||||
1
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
it('Array', () => {
|
||||
assert.deepEqual(
|
||||
DICTADD.transformArguments('dictionary', ['1', '2']),
|
||||
['FT.DICTADD', 'dictionary', '1', '2']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.dictAdd', async client => {
|
||||
assert.equal(
|
||||
await client.ft.dictAdd('dictionary', 'term'),
|
||||
1
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
||||
|
@@ -1,8 +1,11 @@
|
||||
import { RedisCommandArguments } from '@redis/client/dist/lib/commands';
|
||||
import { pushVerdictArguments } from '@redis/client/dist/lib/commands/generic-transformers';
|
||||
import { RedisArgument, NumberReply, Command } from '@redis/client/dist/lib/RESP/types';
|
||||
import { pushVariadicArguments, RedisVariadicArgument } from '@redis/client/dist/lib/commands/generic-transformers';
|
||||
|
||||
export function transformArguments(dictionary: string, term: string | Array<string>): RedisCommandArguments {
|
||||
return pushVerdictArguments(['FT.DICTADD', dictionary], term);
|
||||
}
|
||||
|
||||
export declare function transformReply(): number;
|
||||
export default {
|
||||
FIRST_KEY_INDEX: undefined,
|
||||
IS_READ_ONLY: true,
|
||||
transformArguments(dictionary: RedisArgument, term: RedisVariadicArgument) {
|
||||
return pushVariadicArguments(['FT.DICTADD', dictionary], term);
|
||||
},
|
||||
transformReply: undefined as unknown as () => NumberReply
|
||||
} as const satisfies Command;
|
||||
|
@@ -1,28 +1,28 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { transformArguments } from './DICTDEL';
|
||||
import DICTDEL from './DICTDEL';
|
||||
|
||||
describe('DICTDEL', () => {
|
||||
describe('transformArguments', () => {
|
||||
it('string', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('dictionary', 'term'),
|
||||
['FT.DICTDEL', 'dictionary', 'term']
|
||||
);
|
||||
});
|
||||
|
||||
it('Array', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('dictionary', ['1', '2']),
|
||||
['FT.DICTDEL', 'dictionary', '1', '2']
|
||||
);
|
||||
});
|
||||
describe('FT.DICTDEL', () => {
|
||||
describe('transformArguments', () => {
|
||||
it('string', () => {
|
||||
assert.deepEqual(
|
||||
DICTDEL.transformArguments('dictionary', 'term'),
|
||||
['FT.DICTDEL', 'dictionary', 'term']
|
||||
);
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.dictDel', async client => {
|
||||
assert.equal(
|
||||
await client.ft.dictDel('dictionary', 'term'),
|
||||
0
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
it('Array', () => {
|
||||
assert.deepEqual(
|
||||
DICTDEL.transformArguments('dictionary', ['1', '2']),
|
||||
['FT.DICTDEL', 'dictionary', '1', '2']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.dictDel', async client => {
|
||||
assert.equal(
|
||||
await client.ft.dictDel('dictionary', 'term'),
|
||||
0
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
||||
|
@@ -1,8 +1,11 @@
|
||||
import { RedisCommandArguments } from '@redis/client/dist/lib/commands';
|
||||
import { pushVerdictArguments } from '@redis/client/dist/lib/commands/generic-transformers';
|
||||
import { RedisArgument, NumberReply, Command } from '@redis/client/dist/lib/RESP/types';
|
||||
import { pushVariadicArguments, RedisVariadicArgument } from '@redis/client/dist/lib/commands/generic-transformers';
|
||||
|
||||
export function transformArguments(dictionary: string, term: string | Array<string>): RedisCommandArguments {
|
||||
return pushVerdictArguments(['FT.DICTDEL', dictionary], term);
|
||||
}
|
||||
|
||||
export declare function transformReply(): number;
|
||||
export default {
|
||||
FIRST_KEY_INDEX: undefined,
|
||||
IS_READ_ONLY: true,
|
||||
transformArguments(dictionary: RedisArgument, term: RedisVariadicArgument) {
|
||||
return pushVariadicArguments(['FT.DICTDEL', dictionary], term);
|
||||
},
|
||||
transformReply: undefined as unknown as () => NumberReply
|
||||
} as const satisfies Command;
|
||||
|
@@ -1,21 +1,21 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { transformArguments } from './DICTDUMP';
|
||||
import DICTDUMP from './DICTDUMP';
|
||||
|
||||
describe('DICTDUMP', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('dictionary'),
|
||||
['FT.DICTDUMP', 'dictionary']
|
||||
);
|
||||
});
|
||||
describe('FT.DICTDUMP', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
DICTDUMP.transformArguments('dictionary'),
|
||||
['FT.DICTDUMP', 'dictionary']
|
||||
);
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.dictDump', async client => {
|
||||
await client.ft.dictAdd('dictionary', 'string')
|
||||
testUtils.testWithClient('client.ft.dictDump', async client => {
|
||||
const [, reply] = await Promise.all([
|
||||
client.ft.dictAdd('dictionary', 'string'),
|
||||
client.ft.dictDump('dictionary')
|
||||
]);
|
||||
|
||||
assert.deepEqual(
|
||||
await client.ft.dictDump('dictionary'),
|
||||
['string']
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
assert.deepEqual(reply, ['string']);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
||||
|
@@ -1,5 +1,13 @@
|
||||
export function transformArguments(dictionary: string): Array<string> {
|
||||
return ['FT.DICTDUMP', dictionary];
|
||||
}
|
||||
import { RedisArgument, ArrayReply, SetReply, BlobStringReply, Command } from '@redis/client/dist/lib/RESP/types';
|
||||
|
||||
export declare function transformReply(): Array<string>;
|
||||
export default {
|
||||
FIRST_KEY_INDEX: undefined,
|
||||
IS_READ_ONLY: true,
|
||||
transformArguments(dictionary: RedisArgument) {
|
||||
return ['FT.DICTDUMP', dictionary];
|
||||
},
|
||||
transformReply: {
|
||||
2: undefined as unknown as () => ArrayReply<BlobStringReply>,
|
||||
3: undefined as unknown as () => SetReply<BlobStringReply>
|
||||
}
|
||||
} as const satisfies Command;
|
||||
|
@@ -1,33 +1,33 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { SchemaFieldTypes } from '.';
|
||||
import { transformArguments } from './DROPINDEX';
|
||||
import DROPINDEX from './DROPINDEX';
|
||||
import { SCHEMA_FIELD_TYPE } from './CREATE';
|
||||
|
||||
describe('DROPINDEX', () => {
|
||||
describe('transformArguments', () => {
|
||||
it('without options', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index'),
|
||||
['FT.DROPINDEX', 'index']
|
||||
);
|
||||
});
|
||||
|
||||
it('with DD', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', { DD: true }),
|
||||
['FT.DROPINDEX', 'index', 'DD']
|
||||
);
|
||||
});
|
||||
describe('FT.DROPINDEX', () => {
|
||||
describe('transformArguments', () => {
|
||||
it('without options', () => {
|
||||
assert.deepEqual(
|
||||
DROPINDEX.transformArguments('index'),
|
||||
['FT.DROPINDEX', 'index']
|
||||
);
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.dropIndex', async client => {
|
||||
await client.ft.create('index', {
|
||||
field: SchemaFieldTypes.TEXT
|
||||
});
|
||||
it('with DD', () => {
|
||||
assert.deepEqual(
|
||||
DROPINDEX.transformArguments('index', { DD: true }),
|
||||
['FT.DROPINDEX', 'index', 'DD']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
assert.equal(
|
||||
await client.ft.dropIndex('index'),
|
||||
'OK'
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
testUtils.testWithClient('client.ft.dropIndex', async client => {
|
||||
const [, reply] = await Promise.all([
|
||||
client.ft.create('index', {
|
||||
field: SCHEMA_FIELD_TYPE.TEXT
|
||||
}),
|
||||
client.ft.dropIndex('index')
|
||||
]);
|
||||
|
||||
assert.equal(reply, 'OK');
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
||||
|
@@ -1,15 +1,23 @@
|
||||
interface DropIndexOptions {
|
||||
DD?: true;
|
||||
import { RedisArgument, SimpleStringReply, NumberReply, Command } from '@redis/client/dist/lib/RESP/types';
|
||||
|
||||
export interface FtDropIndexOptions {
|
||||
DD?: true;
|
||||
}
|
||||
|
||||
export function transformArguments(index: string, options?: DropIndexOptions): Array<string> {
|
||||
export default {
|
||||
FIRST_KEY_INDEX: undefined,
|
||||
IS_READ_ONLY: true,
|
||||
transformArguments(index: RedisArgument, options?: FtDropIndexOptions) {
|
||||
const args = ['FT.DROPINDEX', index];
|
||||
|
||||
if (options?.DD) {
|
||||
args.push('DD');
|
||||
args.push('DD');
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
export declare function transformReply(): 'OK';
|
||||
},
|
||||
transformReply: {
|
||||
2: undefined as unknown as () => SimpleStringReply<'OK'>,
|
||||
3: undefined as unknown as () => NumberReply
|
||||
}
|
||||
} as const satisfies Command;
|
||||
|
@@ -1,33 +1,46 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import { transformArguments } from './EXPLAIN';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import EXPLAIN from './EXPLAIN';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { SCHEMA_FIELD_TYPE } from './CREATE';
|
||||
|
||||
describe('EXPLAIN', () => {
|
||||
describe('transformArguments', () => {
|
||||
it('simple', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', '*'),
|
||||
['FT.EXPLAIN', 'index', '*']
|
||||
);
|
||||
});
|
||||
|
||||
it('with PARAMS', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', '*', {
|
||||
PARAMS: {
|
||||
param: 'value'
|
||||
}
|
||||
}),
|
||||
['FT.EXPLAIN', 'index', '*', 'PARAMS', '2', 'param', 'value']
|
||||
);
|
||||
});
|
||||
|
||||
it('with DIALECT', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', '*', {
|
||||
DIALECT: 1
|
||||
}),
|
||||
['FT.EXPLAIN', 'index', '*', 'DIALECT', '1']
|
||||
);
|
||||
});
|
||||
describe('transformArguments', () => {
|
||||
it('simple', () => {
|
||||
assert.deepEqual(
|
||||
EXPLAIN.transformArguments('index', '*'),
|
||||
['FT.EXPLAIN', 'index', '*']
|
||||
);
|
||||
});
|
||||
|
||||
it('with PARAMS', () => {
|
||||
assert.deepEqual(
|
||||
EXPLAIN.transformArguments('index', '*', {
|
||||
PARAMS: {
|
||||
param: 'value'
|
||||
}
|
||||
}),
|
||||
['FT.EXPLAIN', 'index', '*', 'PARAMS', '2', 'param', 'value']
|
||||
);
|
||||
});
|
||||
|
||||
it('with DIALECT', () => {
|
||||
assert.deepEqual(
|
||||
EXPLAIN.transformArguments('index', '*', {
|
||||
DIALECT: 1
|
||||
}),
|
||||
['FT.EXPLAIN', 'index', '*', 'DIALECT', '1']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.dropIndex', async client => {
|
||||
const [, reply] = await Promise.all([
|
||||
client.ft.create('index', {
|
||||
field: SCHEMA_FIELD_TYPE.TEXT
|
||||
}),
|
||||
client.ft.explain('index', '*')
|
||||
]);
|
||||
|
||||
assert.equal(reply, '<WILDCARD>\n');
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
||||
|
@@ -1,26 +1,28 @@
|
||||
import { Params, pushParamsArgs } from ".";
|
||||
import { RedisArgument, SimpleStringReply, Command } from '@redis/client/dist/lib/RESP/types';
|
||||
import { FtSearchParams, pushParamsArgument } from './SEARCH';
|
||||
|
||||
export const IS_READ_ONLY = true;
|
||||
|
||||
interface ExplainOptions {
|
||||
PARAMS?: Params;
|
||||
DIALECT?: number;
|
||||
export interface FtExplainOptions {
|
||||
PARAMS?: FtSearchParams;
|
||||
DIALECT?: number;
|
||||
}
|
||||
|
||||
export function transformArguments(
|
||||
index: string,
|
||||
query: string,
|
||||
options?: ExplainOptions
|
||||
): Array<string> {
|
||||
export default {
|
||||
FIRST_KEY_INDEX: undefined,
|
||||
IS_READ_ONLY: true,
|
||||
transformArguments(
|
||||
index: RedisArgument,
|
||||
query: RedisArgument,
|
||||
options?: FtExplainOptions
|
||||
) {
|
||||
const args = ['FT.EXPLAIN', index, query];
|
||||
|
||||
pushParamsArgs(args, options?.PARAMS);
|
||||
pushParamsArgument(args, options?.PARAMS);
|
||||
|
||||
if (options?.DIALECT) {
|
||||
args.push('DIALECT', options.DIALECT.toString());
|
||||
args.push('DIALECT', options.DIALECT.toString());
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
export declare function transformReply(): string;
|
||||
},
|
||||
transformReply: undefined as unknown as () => SimpleStringReply
|
||||
} as const satisfies Command;
|
||||
|
@@ -1,11 +1,11 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import { transformArguments } from './EXPLAINCLI';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import EXPLAINCLI from './EXPLAINCLI';
|
||||
|
||||
describe('EXPLAINCLI', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', '*'),
|
||||
['FT.EXPLAINCLI', 'index', '*']
|
||||
);
|
||||
});
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
EXPLAINCLI.transformArguments('index', '*'),
|
||||
['FT.EXPLAINCLI', 'index', '*']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@@ -1,7 +1,10 @@
|
||||
export const IS_READ_ONLY = true;
|
||||
import { RedisArgument, ArrayReply, BlobStringReply, Command } from '@redis/client/dist/lib/RESP/types';
|
||||
|
||||
export function transformArguments(index: string, query: string): Array<string> {
|
||||
export default {
|
||||
FIRST_KEY_INDEX: undefined,
|
||||
IS_READ_ONLY: true,
|
||||
transformArguments(index: RedisArgument, query: RedisArgument) {
|
||||
return ['FT.EXPLAINCLI', index, query];
|
||||
}
|
||||
|
||||
export declare function transformReply(): Array<string>;
|
||||
},
|
||||
transformReply: undefined as unknown as () => ArrayReply<BlobStringReply>
|
||||
} as const satisfies Command;
|
||||
|
@@ -1,26 +1,28 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import { SchemaFieldTypes } from '.';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { transformArguments } from './INFO';
|
||||
import INFO, { InfoReply } from './INFO';
|
||||
import { SCHEMA_FIELD_TYPE } from './CREATE';
|
||||
|
||||
describe('INFO', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index'),
|
||||
INFO.transformArguments('index'),
|
||||
['FT.INFO', 'index']
|
||||
);
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.info', async client => {
|
||||
await client.ft.create('index', {
|
||||
field: SchemaFieldTypes.TEXT
|
||||
field: SCHEMA_FIELD_TYPE.TEXT
|
||||
});
|
||||
const ret = await client.ft.info('index');
|
||||
// effectively testing that stopwords_list is not in ret
|
||||
assert.deepEqual(
|
||||
await client.ft.info('index'),
|
||||
ret,
|
||||
{
|
||||
indexName: 'index',
|
||||
indexOptions: [],
|
||||
indexDefinition: Object.create(null, {
|
||||
index_name: 'index',
|
||||
index_options: [],
|
||||
index_definition: Object.create(null, {
|
||||
default_score: {
|
||||
value: '1',
|
||||
configurable: true,
|
||||
@@ -59,41 +61,48 @@ describe('INFO', () => {
|
||||
enumerable: true
|
||||
}
|
||||
})],
|
||||
numDocs: '0',
|
||||
maxDocId: '0',
|
||||
numTerms: '0',
|
||||
numRecords: '0',
|
||||
invertedSzMb: '0',
|
||||
vectorIndexSzMb: '0',
|
||||
totalInvertedIndexBlocks: '0',
|
||||
offsetVectorsSzMb: '0',
|
||||
docTableSizeMb: '0',
|
||||
sortableValuesSizeMb: '0',
|
||||
keyTableSizeMb: '0',
|
||||
recordsPerDocAvg: '-nan',
|
||||
bytesPerRecordAvg: '-nan',
|
||||
offsetsPerTermAvg: '-nan',
|
||||
offsetBitsPerRecordAvg: '-nan',
|
||||
hashIndexingFailures: '0',
|
||||
indexing: '0',
|
||||
percentIndexed: '1',
|
||||
gcStats: {
|
||||
bytesCollected: '0',
|
||||
totalMsRun: '0',
|
||||
totalCycles: '0',
|
||||
averageCycleTimeMs: '-nan',
|
||||
lastRunTimeMs: '0',
|
||||
gcNumericTreesMissed: '0',
|
||||
gcBlocksDenied: '0'
|
||||
num_docs: 0,
|
||||
max_doc_id: 0,
|
||||
num_terms: 0,
|
||||
num_records: 0,
|
||||
inverted_sz_mb: 0,
|
||||
vector_index_sz_mb: 0,
|
||||
total_inverted_index_blocks: 0,
|
||||
offset_vectors_sz_mb: 0,
|
||||
doc_table_size_mb: 0,
|
||||
sortable_values_size_mb: 0,
|
||||
key_table_size_mb: 0,
|
||||
records_per_doc_avg: NaN,
|
||||
bytes_per_record_avg: NaN,
|
||||
cleaning: 0,
|
||||
offsets_per_term_avg: NaN,
|
||||
offset_bits_per_record_avg: NaN,
|
||||
geoshapes_sz_mb: 0,
|
||||
hash_indexing_failures: 0,
|
||||
indexing: 0,
|
||||
percent_indexed: 1,
|
||||
number_of_uses: 1,
|
||||
tag_overhead_sz_mb: 0,
|
||||
text_overhead_sz_mb: 0,
|
||||
total_index_memory_sz_mb: 0,
|
||||
total_indexing_time: 0,
|
||||
gc_stats: {
|
||||
bytes_collected: 0,
|
||||
total_ms_run: 0,
|
||||
total_cycles: 0,
|
||||
average_cycle_time_ms: NaN,
|
||||
last_run_time_ms: 0,
|
||||
gc_numeric_trees_missed: 0,
|
||||
gc_blocks_denied: 0
|
||||
},
|
||||
cursorStats: {
|
||||
globalIdle: 0,
|
||||
globalTotal: 0,
|
||||
indexCapacity: 128,
|
||||
idnexTotal: 0
|
||||
cursor_stats: {
|
||||
global_idle: 0,
|
||||
global_total: 0,
|
||||
index_capacity: 128,
|
||||
index_total: 0
|
||||
},
|
||||
stopWords: undefined
|
||||
}
|
||||
);
|
||||
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
||||
|
@@ -1,167 +1,163 @@
|
||||
import { RedisCommandArgument } from '@redis/client/dist/lib/commands';
|
||||
import { transformTuplesReply } from '@redis/client/dist/lib/commands/generic-transformers';
|
||||
import { RedisArgument } from "@redis/client";
|
||||
import { ArrayReply, BlobStringReply, Command, DoubleReply, MapReply, NullReply, NumberReply, ReplyUnion, SimpleStringReply, TypeMapping } from "@redis/client/dist/lib/RESP/types";
|
||||
import { createTransformTuplesReplyFunc, transformDoubleReply } from "@redis/client/dist/lib/commands/generic-transformers";
|
||||
import { TuplesReply } from '@redis/client/lib/RESP/types';
|
||||
|
||||
export function transformArguments(index: string): Array<string> {
|
||||
export default {
|
||||
FIRST_KEY_INDEX: undefined,
|
||||
IS_READ_ONLY: true,
|
||||
transformArguments(index: RedisArgument) {
|
||||
return ['FT.INFO', index];
|
||||
},
|
||||
transformReply: {
|
||||
2: transformV2Reply,
|
||||
3: undefined as unknown as () => ReplyUnion
|
||||
},
|
||||
unstableResp3: true
|
||||
} as const satisfies Command;
|
||||
|
||||
export interface InfoReply {
|
||||
index_name: SimpleStringReply;
|
||||
index_options: ArrayReply<SimpleStringReply>;
|
||||
index_definition: MapReply<SimpleStringReply, SimpleStringReply>;
|
||||
attributes: Array<MapReply<SimpleStringReply, SimpleStringReply>>;
|
||||
num_docs: NumberReply
|
||||
max_doc_id: NumberReply;
|
||||
num_terms: NumberReply;
|
||||
num_records: NumberReply;
|
||||
inverted_sz_mb: DoubleReply;
|
||||
vector_index_sz_mb: DoubleReply;
|
||||
total_inverted_index_blocks: NumberReply;
|
||||
offset_vectors_sz_mb: DoubleReply;
|
||||
doc_table_size_mb: DoubleReply;
|
||||
sortable_values_size_mb: DoubleReply;
|
||||
key_table_size_mb: DoubleReply;
|
||||
tag_overhead_sz_mb: DoubleReply;
|
||||
text_overhead_sz_mb: DoubleReply;
|
||||
total_index_memory_sz_mb: DoubleReply;
|
||||
geoshapes_sz_mb: DoubleReply;
|
||||
records_per_doc_avg: DoubleReply;
|
||||
bytes_per_record_avg: DoubleReply;
|
||||
offsets_per_term_avg: DoubleReply;
|
||||
offset_bits_per_record_avg: DoubleReply;
|
||||
hash_indexing_failures: NumberReply;
|
||||
total_indexing_time: DoubleReply;
|
||||
indexing: NumberReply;
|
||||
percent_indexed: DoubleReply;
|
||||
number_of_uses: NumberReply;
|
||||
cleaning: NumberReply;
|
||||
gc_stats: {
|
||||
bytes_collected: DoubleReply;
|
||||
total_ms_run: DoubleReply;
|
||||
total_cycles: DoubleReply;
|
||||
average_cycle_time_ms: DoubleReply;
|
||||
last_run_time_ms: DoubleReply;
|
||||
gc_numeric_trees_missed: DoubleReply;
|
||||
gc_blocks_denied: DoubleReply;
|
||||
};
|
||||
cursor_stats: {
|
||||
global_idle: NumberReply;
|
||||
global_total: NumberReply;
|
||||
index_capacity: NumberReply;
|
||||
index_total: NumberReply;
|
||||
};
|
||||
stopwords_list?: ArrayReply<BlobStringReply> | TuplesReply<[NullReply]>;
|
||||
}
|
||||
|
||||
type InfoRawReply = [
|
||||
'index_name',
|
||||
RedisCommandArgument,
|
||||
'index_options',
|
||||
Array<RedisCommandArgument>,
|
||||
'index_definition',
|
||||
Array<RedisCommandArgument>,
|
||||
'attributes',
|
||||
Array<Array<RedisCommandArgument>>,
|
||||
'num_docs',
|
||||
RedisCommandArgument,
|
||||
'max_doc_id',
|
||||
RedisCommandArgument,
|
||||
'num_terms',
|
||||
RedisCommandArgument,
|
||||
'num_records',
|
||||
RedisCommandArgument,
|
||||
'inverted_sz_mb',
|
||||
RedisCommandArgument,
|
||||
'vector_index_sz_mb',
|
||||
RedisCommandArgument,
|
||||
'total_inverted_index_blocks',
|
||||
RedisCommandArgument,
|
||||
'offset_vectors_sz_mb',
|
||||
RedisCommandArgument,
|
||||
'doc_table_size_mb',
|
||||
RedisCommandArgument,
|
||||
'sortable_values_size_mb',
|
||||
RedisCommandArgument,
|
||||
'key_table_size_mb',
|
||||
RedisCommandArgument,
|
||||
'records_per_doc_avg',
|
||||
RedisCommandArgument,
|
||||
'bytes_per_record_avg',
|
||||
RedisCommandArgument,
|
||||
'offsets_per_term_avg',
|
||||
RedisCommandArgument,
|
||||
'offset_bits_per_record_avg',
|
||||
RedisCommandArgument,
|
||||
'hash_indexing_failures',
|
||||
RedisCommandArgument,
|
||||
'indexing',
|
||||
RedisCommandArgument,
|
||||
'percent_indexed',
|
||||
RedisCommandArgument,
|
||||
'gc_stats',
|
||||
[
|
||||
'bytes_collected',
|
||||
RedisCommandArgument,
|
||||
'total_ms_run',
|
||||
RedisCommandArgument,
|
||||
'total_cycles',
|
||||
RedisCommandArgument,
|
||||
'average_cycle_time_ms',
|
||||
RedisCommandArgument,
|
||||
'last_run_time_ms',
|
||||
RedisCommandArgument,
|
||||
'gc_numeric_trees_missed',
|
||||
RedisCommandArgument,
|
||||
'gc_blocks_denied',
|
||||
RedisCommandArgument
|
||||
],
|
||||
'cursor_stats',
|
||||
[
|
||||
'global_idle',
|
||||
number,
|
||||
'global_total',
|
||||
number,
|
||||
'index_capacity',
|
||||
number,
|
||||
'index_total',
|
||||
number
|
||||
],
|
||||
'stopwords_list'?,
|
||||
Array<RedisCommandArgument>?
|
||||
];
|
||||
function transformV2Reply(reply: Array<any>, preserve?: any, typeMapping?: TypeMapping): InfoReply {
|
||||
const myTransformFunc = createTransformTuplesReplyFunc<SimpleStringReply>(preserve, typeMapping);
|
||||
|
||||
interface InfoReply {
|
||||
indexName: RedisCommandArgument;
|
||||
indexOptions: Array<RedisCommandArgument>;
|
||||
indexDefinition: Record<string, RedisCommandArgument>;
|
||||
attributes: Array<Record<string, RedisCommandArgument>>;
|
||||
numDocs: RedisCommandArgument;
|
||||
maxDocId: RedisCommandArgument;
|
||||
numTerms: RedisCommandArgument;
|
||||
numRecords: RedisCommandArgument;
|
||||
invertedSzMb: RedisCommandArgument;
|
||||
vectorIndexSzMb: RedisCommandArgument;
|
||||
totalInvertedIndexBlocks: RedisCommandArgument;
|
||||
offsetVectorsSzMb: RedisCommandArgument;
|
||||
docTableSizeMb: RedisCommandArgument;
|
||||
sortableValuesSizeMb: RedisCommandArgument;
|
||||
keyTableSizeMb: RedisCommandArgument;
|
||||
recordsPerDocAvg: RedisCommandArgument;
|
||||
bytesPerRecordAvg: RedisCommandArgument;
|
||||
offsetsPerTermAvg: RedisCommandArgument;
|
||||
offsetBitsPerRecordAvg: RedisCommandArgument;
|
||||
hashIndexingFailures: RedisCommandArgument;
|
||||
indexing: RedisCommandArgument;
|
||||
percentIndexed: RedisCommandArgument;
|
||||
gcStats: {
|
||||
bytesCollected: RedisCommandArgument;
|
||||
totalMsRun: RedisCommandArgument;
|
||||
totalCycles: RedisCommandArgument;
|
||||
averageCycleTimeMs: RedisCommandArgument;
|
||||
lastRunTimeMs: RedisCommandArgument;
|
||||
gcNumericTreesMissed: RedisCommandArgument;
|
||||
gcBlocksDenied: RedisCommandArgument;
|
||||
};
|
||||
cursorStats: {
|
||||
globalIdle: number;
|
||||
globalTotal: number;
|
||||
indexCapacity: number;
|
||||
idnexTotal: number;
|
||||
};
|
||||
stopWords: Array<RedisCommandArgument> | undefined;
|
||||
}
|
||||
const ret = {} as unknown as InfoReply;
|
||||
|
||||
export function transformReply(rawReply: InfoRawReply): InfoReply {
|
||||
return {
|
||||
indexName: rawReply[1],
|
||||
indexOptions: rawReply[3],
|
||||
indexDefinition: transformTuplesReply(rawReply[5]),
|
||||
attributes: rawReply[7].map(attribute => transformTuplesReply(attribute)),
|
||||
numDocs: rawReply[9],
|
||||
maxDocId: rawReply[11],
|
||||
numTerms: rawReply[13],
|
||||
numRecords: rawReply[15],
|
||||
invertedSzMb: rawReply[17],
|
||||
vectorIndexSzMb: rawReply[19],
|
||||
totalInvertedIndexBlocks: rawReply[21],
|
||||
offsetVectorsSzMb: rawReply[23],
|
||||
docTableSizeMb: rawReply[25],
|
||||
sortableValuesSizeMb: rawReply[27],
|
||||
keyTableSizeMb: rawReply[29],
|
||||
recordsPerDocAvg: rawReply[31],
|
||||
bytesPerRecordAvg: rawReply[33],
|
||||
offsetsPerTermAvg: rawReply[35],
|
||||
offsetBitsPerRecordAvg: rawReply[37],
|
||||
hashIndexingFailures: rawReply[39],
|
||||
indexing: rawReply[41],
|
||||
percentIndexed: rawReply[43],
|
||||
gcStats: {
|
||||
bytesCollected: rawReply[45][1],
|
||||
totalMsRun: rawReply[45][3],
|
||||
totalCycles: rawReply[45][5],
|
||||
averageCycleTimeMs: rawReply[45][7],
|
||||
lastRunTimeMs: rawReply[45][9],
|
||||
gcNumericTreesMissed: rawReply[45][11],
|
||||
gcBlocksDenied: rawReply[45][13]
|
||||
},
|
||||
cursorStats: {
|
||||
globalIdle: rawReply[47][1],
|
||||
globalTotal: rawReply[47][3],
|
||||
indexCapacity: rawReply[47][5],
|
||||
idnexTotal: rawReply[47][7]
|
||||
},
|
||||
stopWords: rawReply[49]
|
||||
};
|
||||
for (let i=0; i < reply.length; i += 2) {
|
||||
const key = reply[i].toString() as keyof InfoReply;
|
||||
|
||||
switch (key) {
|
||||
case 'index_name':
|
||||
case 'index_options':
|
||||
case 'num_docs':
|
||||
case 'max_doc_id':
|
||||
case 'num_terms':
|
||||
case 'num_records':
|
||||
case 'total_inverted_index_blocks':
|
||||
case 'hash_indexing_failures':
|
||||
case 'indexing':
|
||||
case 'number_of_uses':
|
||||
case 'cleaning':
|
||||
case 'stopwords_list':
|
||||
ret[key] = reply[i+1];
|
||||
break;
|
||||
case 'inverted_sz_mb':
|
||||
case 'vector_index_sz_mb':
|
||||
case 'offset_vectors_sz_mb':
|
||||
case 'doc_table_size_mb':
|
||||
case 'sortable_values_size_mb':
|
||||
case 'key_table_size_mb':
|
||||
case 'text_overhead_sz_mb':
|
||||
case 'tag_overhead_sz_mb':
|
||||
case 'total_index_memory_sz_mb':
|
||||
case 'geoshapes_sz_mb':
|
||||
case 'records_per_doc_avg':
|
||||
case 'bytes_per_record_avg':
|
||||
case 'offsets_per_term_avg':
|
||||
case 'offset_bits_per_record_avg':
|
||||
case 'total_indexing_time':
|
||||
case 'percent_indexed':
|
||||
ret[key] = transformDoubleReply[2](reply[i+1], undefined, typeMapping) as DoubleReply;
|
||||
break;
|
||||
case 'index_definition':
|
||||
ret[key] = myTransformFunc(reply[i+1]);
|
||||
break;
|
||||
case 'attributes':
|
||||
ret[key] = (reply[i+1] as Array<ArrayReply<SimpleStringReply>>).map(attribute => myTransformFunc(attribute));
|
||||
break;
|
||||
case 'gc_stats': {
|
||||
const innerRet = {} as unknown as InfoReply['gc_stats'];
|
||||
|
||||
const array = reply[i+1];
|
||||
|
||||
for (let i=0; i < array.length; i += 2) {
|
||||
const innerKey = array[i].toString() as keyof InfoReply['gc_stats'];
|
||||
|
||||
switch (innerKey) {
|
||||
case 'bytes_collected':
|
||||
case 'total_ms_run':
|
||||
case 'total_cycles':
|
||||
case 'average_cycle_time_ms':
|
||||
case 'last_run_time_ms':
|
||||
case 'gc_numeric_trees_missed':
|
||||
case 'gc_blocks_denied':
|
||||
innerRet[innerKey] = transformDoubleReply[2](array[i+1], undefined, typeMapping) as DoubleReply;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ret[key] = innerRet;
|
||||
break;
|
||||
}
|
||||
case 'cursor_stats': {
|
||||
const innerRet = {} as unknown as InfoReply['cursor_stats'];
|
||||
|
||||
const array = reply[i+1];
|
||||
|
||||
for (let i=0; i < array.length; i += 2) {
|
||||
const innerKey = array[i].toString() as keyof InfoReply['cursor_stats'];
|
||||
|
||||
switch (innerKey) {
|
||||
case 'global_idle':
|
||||
case 'global_total':
|
||||
case 'index_capacity':
|
||||
case 'index_total':
|
||||
innerRet[innerKey] = array[i+1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ret[key] = innerRet;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@@ -1,25 +1,25 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { SchemaFieldTypes } from '.';
|
||||
import { transformArguments } from './PROFILE_AGGREGATE';
|
||||
import { AggregateSteps } from './AGGREGATE';
|
||||
import { FT_AGGREGATE_STEPS } from './AGGREGATE';
|
||||
import PROFILE_AGGREGATE from './PROFILE_AGGREGATE';
|
||||
import { SCHEMA_FIELD_TYPE } from './CREATE';
|
||||
|
||||
describe('PROFILE AGGREGATE', () => {
|
||||
describe('transformArguments', () => {
|
||||
it('without options', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query'),
|
||||
PROFILE_AGGREGATE.transformArguments('index', 'query'),
|
||||
['FT.PROFILE', 'index', 'AGGREGATE', 'QUERY', 'query']
|
||||
);
|
||||
});
|
||||
|
||||
it('with options', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', {
|
||||
PROFILE_AGGREGATE.transformArguments('index', 'query', {
|
||||
LIMITED: true,
|
||||
VERBATIM: true,
|
||||
STEPS: [{
|
||||
type: AggregateSteps.SORTBY,
|
||||
type: FT_AGGREGATE_STEPS.SORTBY,
|
||||
BY: '@by'
|
||||
}]
|
||||
}),
|
||||
@@ -32,13 +32,14 @@ describe('PROFILE AGGREGATE', () => {
|
||||
testUtils.testWithClient('client.ft.search', async client => {
|
||||
await Promise.all([
|
||||
client.ft.create('index', {
|
||||
field: SchemaFieldTypes.NUMERIC
|
||||
field: SCHEMA_FIELD_TYPE.NUMERIC
|
||||
}),
|
||||
client.hSet('1', 'field', '1'),
|
||||
client.hSet('2', 'field', '2')
|
||||
]);
|
||||
|
||||
const res = await client.ft.profileAggregate('index', '*');
|
||||
assert.deepEqual('None', res.profile.warning);
|
||||
assert.ok(typeof res.profile.iteratorsProfile.counter === 'number');
|
||||
assert.ok(typeof res.profile.parsingTime === 'string');
|
||||
assert.ok(res.results.total == 1);
|
||||
|
@@ -1,29 +1,38 @@
|
||||
import { pushAggregatehOptions, AggregateOptions, transformReply as transformAggregateReply, AggregateRawReply } from './AGGREGATE';
|
||||
import { ProfileOptions, ProfileRawReply, ProfileReply, transformProfile } from '.';
|
||||
// import { pushAggregatehOptions, AggregateOptions, transformReply as transformAggregateReply, AggregateRawReply } from './AGGREGATE';
|
||||
// import { ProfileOptions, ProfileRawReply, ProfileReply, transformProfile } from '.';
|
||||
|
||||
export const IS_READ_ONLY = true;
|
||||
import { Command, ReplyUnion } from "@redis/client/dist/lib/RESP/types";
|
||||
import AGGREGATE, { AggregateRawReply, FtAggregateOptions, pushAggregateOptions } from "./AGGREGATE";
|
||||
import { ProfileOptions, ProfileRawReply, ProfileReply, transformProfile } from "./PROFILE_SEARCH";
|
||||
|
||||
export function transformArguments(
|
||||
index: string,
|
||||
query: string,
|
||||
options?: ProfileOptions & AggregateOptions
|
||||
): Array<string> {
|
||||
const args = ['FT.PROFILE', index, 'AGGREGATE'];
|
||||
|
||||
if (options?.LIMITED) {
|
||||
export default {
|
||||
FIRST_KEY_INDEX: undefined,
|
||||
IS_READ_ONLY: true,
|
||||
transformArguments(
|
||||
index: string,
|
||||
query: string,
|
||||
options?: ProfileOptions & FtAggregateOptions
|
||||
) {
|
||||
const args = ['FT.PROFILE', index, 'AGGREGATE'];
|
||||
|
||||
if (options?.LIMITED) {
|
||||
args.push('LIMITED');
|
||||
}
|
||||
}
|
||||
|
||||
args.push('QUERY', query);
|
||||
|
||||
args.push('QUERY', query);
|
||||
pushAggregatehOptions(args, options)
|
||||
return args;
|
||||
}
|
||||
return pushAggregateOptions(args, options)
|
||||
},
|
||||
transformReply: {
|
||||
2: (reply: ProfileAggeregateRawReply): ProfileReply => {
|
||||
return {
|
||||
results: AGGREGATE.transformReply[2](reply[0]),
|
||||
profile: transformProfile(reply[1])
|
||||
}
|
||||
},
|
||||
3: undefined as unknown as () => ReplyUnion
|
||||
},
|
||||
unstableResp3: true
|
||||
} as const satisfies Command;
|
||||
|
||||
type ProfileAggeregateRawReply = ProfileRawReply<AggregateRawReply>;
|
||||
|
||||
export function transformReply(reply: ProfileAggeregateRawReply): ProfileReply {
|
||||
return {
|
||||
results: transformAggregateReply(reply[0]),
|
||||
profile: transformProfile(reply[1])
|
||||
};
|
||||
}
|
||||
type ProfileAggeregateRawReply = ProfileRawReply<AggregateRawReply>;
|
@@ -1,20 +1,21 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { SchemaFieldTypes } from '.';
|
||||
import { transformArguments } from './PROFILE_SEARCH';
|
||||
import PROFILE_SEARCH from './PROFILE_SEARCH';
|
||||
import { SCHEMA_FIELD_TYPE } from './CREATE';
|
||||
|
||||
|
||||
describe('PROFILE SEARCH', () => {
|
||||
describe('transformArguments', () => {
|
||||
it('without options', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query'),
|
||||
PROFILE_SEARCH.transformArguments('index', 'query'),
|
||||
['FT.PROFILE', 'index', 'SEARCH', 'QUERY', 'query']
|
||||
);
|
||||
});
|
||||
|
||||
it('with options', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', {
|
||||
PROFILE_SEARCH.transformArguments('index', 'query', {
|
||||
LIMITED: true,
|
||||
VERBATIM: true,
|
||||
INKEYS: 'key'
|
||||
@@ -28,12 +29,13 @@ describe('PROFILE SEARCH', () => {
|
||||
testUtils.testWithClient('client.ft.search', async client => {
|
||||
await Promise.all([
|
||||
client.ft.create('index', {
|
||||
field: SchemaFieldTypes.NUMERIC
|
||||
field: SCHEMA_FIELD_TYPE.NUMERIC
|
||||
}),
|
||||
client.hSet('1', 'field', '1')
|
||||
]);
|
||||
|
||||
const res = await client.ft.profileSearch('index', '*');
|
||||
assert.strictEqual('None', res.profile.warning);
|
||||
assert.ok(typeof res.profile.iteratorsProfile.counter === 'number');
|
||||
assert.ok(typeof res.profile.parsingTime === 'string');
|
||||
assert.ok(res.results.total == 1);
|
||||
|
@@ -1,29 +1,152 @@
|
||||
import { SearchOptions, SearchRawReply, transformReply as transformSearchReply } from './SEARCH';
|
||||
import { pushSearchOptions, ProfileOptions, ProfileRawReply, ProfileReply, transformProfile } from '.';
|
||||
import { RedisCommandArguments } from '@redis/client/dist/lib/commands';
|
||||
// import { SearchOptions, SearchRawReply, transformReply as transformSearchReply } from './SEARCH';
|
||||
// import { pushSearchOptions, ProfileOptions, ProfileRawReply, ProfileReply, transformProfile } from '.';
|
||||
// import { RedisCommandArguments } from '@redis/client/dist/lib/commands';
|
||||
|
||||
export const IS_READ_ONLY = true;
|
||||
import { Command, RedisArgument, ReplyUnion } from "@redis/client/dist/lib/RESP/types";
|
||||
import SEARCH, { FtSearchOptions, SearchRawReply, SearchReply, pushSearchOptions } from "./SEARCH";
|
||||
import { AggregateReply } from "./AGGREGATE";
|
||||
|
||||
export function transformArguments(
|
||||
index: string,
|
||||
query: string,
|
||||
options?: ProfileOptions & SearchOptions
|
||||
): RedisCommandArguments {
|
||||
let args: RedisCommandArguments = ['FT.PROFILE', index, 'SEARCH'];
|
||||
|
||||
if (options?.LIMITED) {
|
||||
args.push('LIMITED');
|
||||
}
|
||||
|
||||
args.push('QUERY', query);
|
||||
return pushSearchOptions(args, options);
|
||||
}
|
||||
export type ProfileRawReply<T> = [
|
||||
results: T,
|
||||
profile: [
|
||||
_: string,
|
||||
TotalProfileTime: string,
|
||||
_: string,
|
||||
ParsingTime: string,
|
||||
_: string,
|
||||
PipelineCreationTime: string,
|
||||
_: string,
|
||||
IteratorsProfile: Array<any>
|
||||
]
|
||||
];
|
||||
|
||||
type ProfileSearchRawReply = ProfileRawReply<SearchRawReply>;
|
||||
|
||||
export function transformReply(reply: ProfileSearchRawReply, withoutDocuments: boolean): ProfileReply {
|
||||
return {
|
||||
results: transformSearchReply(reply[0], withoutDocuments),
|
||||
profile: transformProfile(reply[1])
|
||||
};
|
||||
export interface ProfileOptions {
|
||||
LIMITED?: true;
|
||||
}
|
||||
|
||||
export default {
|
||||
FIRST_KEY_INDEX: undefined,
|
||||
IS_READ_ONLY: true,
|
||||
transformArguments(
|
||||
index: RedisArgument,
|
||||
query: RedisArgument,
|
||||
options?: ProfileOptions & FtSearchOptions
|
||||
) {
|
||||
let args: Array<RedisArgument> = ['FT.PROFILE', index, 'SEARCH'];
|
||||
|
||||
if (options?.LIMITED) {
|
||||
args.push('LIMITED');
|
||||
}
|
||||
|
||||
args.push('QUERY', query);
|
||||
|
||||
return pushSearchOptions(args, options);
|
||||
},
|
||||
transformReply: {
|
||||
2: (reply: ProfileSearchRawReply, withoutDocuments: boolean): ProfileReply => {
|
||||
return {
|
||||
results: SEARCH.transformReply[2](reply[0]),
|
||||
profile: transformProfile(reply[1])
|
||||
}
|
||||
},
|
||||
3: undefined as unknown as () => ReplyUnion
|
||||
},
|
||||
unstableResp3: true
|
||||
} as const satisfies Command;
|
||||
|
||||
export interface ProfileReply {
|
||||
results: SearchReply | AggregateReply;
|
||||
profile: ProfileData;
|
||||
}
|
||||
|
||||
interface ChildIterator {
|
||||
type?: string,
|
||||
counter?: number,
|
||||
term?: string,
|
||||
size?: number,
|
||||
time?: string,
|
||||
childIterators?: Array<ChildIterator>
|
||||
}
|
||||
|
||||
interface IteratorsProfile {
|
||||
type?: string,
|
||||
counter?: number,
|
||||
queryType?: string,
|
||||
time?: string,
|
||||
childIterators?: Array<ChildIterator>
|
||||
}
|
||||
|
||||
interface ProfileData {
|
||||
totalProfileTime: string,
|
||||
parsingTime: string,
|
||||
pipelineCreationTime: string,
|
||||
warning: string,
|
||||
iteratorsProfile: IteratorsProfile
|
||||
}
|
||||
|
||||
export function transformProfile(reply: Array<any>): ProfileData{
|
||||
return {
|
||||
totalProfileTime: reply[0][1],
|
||||
parsingTime: reply[1][1],
|
||||
pipelineCreationTime: reply[2][1],
|
||||
warning: reply[3][1] ? reply[3][1] : 'None',
|
||||
iteratorsProfile: transformIterators(reply[4][1])
|
||||
};
|
||||
}
|
||||
|
||||
function transformIterators(IteratorsProfile: Array<any>): IteratorsProfile {
|
||||
var res: IteratorsProfile = {};
|
||||
for (let i = 0; i < IteratorsProfile.length; i += 2) {
|
||||
const value = IteratorsProfile[i+1];
|
||||
switch (IteratorsProfile[i]) {
|
||||
case 'Type':
|
||||
res.type = value;
|
||||
break;
|
||||
case 'Counter':
|
||||
res.counter = value;
|
||||
break;
|
||||
case 'Time':
|
||||
res.time = value;
|
||||
break;
|
||||
case 'Query type':
|
||||
res.queryType = value;
|
||||
break;
|
||||
case 'Child iterators':
|
||||
res.childIterators = value.map(transformChildIterators);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
function transformChildIterators(IteratorsProfile: Array<any>): ChildIterator {
|
||||
var res: ChildIterator = {};
|
||||
for (let i = 1; i < IteratorsProfile.length; i += 2) {
|
||||
const value = IteratorsProfile[i+1];
|
||||
switch (IteratorsProfile[i]) {
|
||||
case 'Type':
|
||||
res.type = value;
|
||||
break;
|
||||
case 'Counter':
|
||||
res.counter = value;
|
||||
break;
|
||||
case 'Time':
|
||||
res.time = value;
|
||||
break;
|
||||
case 'Size':
|
||||
res.size = value;
|
||||
break;
|
||||
case 'Term':
|
||||
res.term = value;
|
||||
break;
|
||||
case 'Child iterators':
|
||||
res.childIterators = value.map(transformChildIterators);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
@@ -1,300 +1,327 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import { RedisSearchLanguages, SchemaFieldTypes } from '.';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { transformArguments } from './SEARCH';
|
||||
import SEARCH from './SEARCH';
|
||||
|
||||
describe('SEARCH', () => {
|
||||
describe('transformArguments', () => {
|
||||
it('without options', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query'),
|
||||
['FT.SEARCH', 'index', 'query']
|
||||
);
|
||||
});
|
||||
|
||||
it('with VERBATIM', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', { VERBATIM: true }),
|
||||
['FT.SEARCH', 'index', 'query', 'VERBATIM']
|
||||
);
|
||||
});
|
||||
|
||||
it('with NOSTOPWORDS', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', { NOSTOPWORDS: true }),
|
||||
['FT.SEARCH', 'index', 'query', 'NOSTOPWORDS']
|
||||
);
|
||||
});
|
||||
|
||||
it('with INKEYS', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', { INKEYS: 'key' }),
|
||||
['FT.SEARCH', 'index', 'query', 'INKEYS', '1', 'key']
|
||||
);
|
||||
});
|
||||
|
||||
it('with INFIELDS', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', { INFIELDS: 'field' }),
|
||||
['FT.SEARCH', 'index', 'query', 'INFIELDS', '1', 'field']
|
||||
);
|
||||
});
|
||||
|
||||
it('with RETURN', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', { RETURN: 'return' }),
|
||||
['FT.SEARCH', 'index', 'query', 'RETURN', '1', 'return']
|
||||
);
|
||||
});
|
||||
|
||||
describe('with SUMMARIZE', () => {
|
||||
it('true', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', { SUMMARIZE: true }),
|
||||
['FT.SEARCH', 'index', 'query', 'SUMMARIZE']
|
||||
);
|
||||
});
|
||||
|
||||
describe('with FIELDS', () => {
|
||||
it('string', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', {
|
||||
SUMMARIZE: {
|
||||
FIELDS: ['@field']
|
||||
}
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'SUMMARIZE', 'FIELDS', '1', '@field']
|
||||
);
|
||||
});
|
||||
|
||||
it('Array', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', {
|
||||
SUMMARIZE: {
|
||||
FIELDS: ['@1', '@2']
|
||||
}
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'SUMMARIZE', 'FIELDS', '2', '@1', '@2']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('with FRAGS', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', {
|
||||
SUMMARIZE: {
|
||||
FRAGS: 1
|
||||
}
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'SUMMARIZE', 'FRAGS', '1']
|
||||
);
|
||||
});
|
||||
|
||||
it('with LEN', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', {
|
||||
SUMMARIZE: {
|
||||
LEN: 1
|
||||
}
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'SUMMARIZE', 'LEN', '1']
|
||||
);
|
||||
});
|
||||
|
||||
it('with SEPARATOR', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', {
|
||||
SUMMARIZE: {
|
||||
SEPARATOR: 'separator'
|
||||
}
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'SUMMARIZE', 'SEPARATOR', 'separator']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with HIGHLIGHT', () => {
|
||||
it('true', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', { HIGHLIGHT: true }),
|
||||
['FT.SEARCH', 'index', 'query', 'HIGHLIGHT']
|
||||
);
|
||||
});
|
||||
|
||||
describe('with FIELDS', () => {
|
||||
it('string', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', {
|
||||
HIGHLIGHT: {
|
||||
FIELDS: ['@field']
|
||||
}
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'HIGHLIGHT', 'FIELDS', '1', '@field']
|
||||
);
|
||||
});
|
||||
|
||||
it('Array', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', {
|
||||
HIGHLIGHT: {
|
||||
FIELDS: ['@1', '@2']
|
||||
}
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'HIGHLIGHT', 'FIELDS', '2', '@1', '@2']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('with TAGS', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', {
|
||||
HIGHLIGHT: {
|
||||
TAGS: {
|
||||
open: 'open',
|
||||
close: 'close'
|
||||
}
|
||||
}
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'HIGHLIGHT', 'TAGS', 'open', 'close']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('with SLOP', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', { SLOP: 1 }),
|
||||
['FT.SEARCH', 'index', 'query', 'SLOP', '1']
|
||||
);
|
||||
});
|
||||
|
||||
it('with INORDER', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', { INORDER: true }),
|
||||
['FT.SEARCH', 'index', 'query', 'INORDER']
|
||||
);
|
||||
});
|
||||
|
||||
it('with LANGUAGE', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', { LANGUAGE: RedisSearchLanguages.ARABIC }),
|
||||
['FT.SEARCH', 'index', 'query', 'LANGUAGE', RedisSearchLanguages.ARABIC]
|
||||
);
|
||||
});
|
||||
|
||||
it('with EXPANDER', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', { EXPANDER: 'expender' }),
|
||||
['FT.SEARCH', 'index', 'query', 'EXPANDER', 'expender']
|
||||
);
|
||||
});
|
||||
|
||||
it('with SCORER', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', { SCORER: 'scorer' }),
|
||||
['FT.SEARCH', 'index', 'query', 'SCORER', 'scorer']
|
||||
);
|
||||
});
|
||||
|
||||
it('with SORTBY', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', { SORTBY: '@by' }),
|
||||
['FT.SEARCH', 'index', 'query', 'SORTBY', '@by']
|
||||
);
|
||||
});
|
||||
|
||||
it('with LIMIT', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', {
|
||||
LIMIT: {
|
||||
from: 0,
|
||||
size: 1
|
||||
}
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'LIMIT', '0', '1']
|
||||
);
|
||||
});
|
||||
|
||||
it('with PARAMS', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', {
|
||||
PARAMS: {
|
||||
param: 'value'
|
||||
}
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'PARAMS', '2', 'param', 'value']
|
||||
);
|
||||
});
|
||||
|
||||
it('with DIALECT', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', {
|
||||
DIALECT: 1
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'DIALECT', '1']
|
||||
);
|
||||
});
|
||||
|
||||
it('with TIMEOUT', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', {
|
||||
TIMEOUT: 5
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'TIMEOUT', '5']
|
||||
);
|
||||
});
|
||||
describe('FT.SEARCH', () => {
|
||||
describe('transformArguments', () => {
|
||||
it('without options', () => {
|
||||
assert.deepEqual(
|
||||
SEARCH.transformArguments('index', 'query'),
|
||||
['FT.SEARCH', 'index', 'query']
|
||||
);
|
||||
});
|
||||
|
||||
describe('client.ft.search', () => {
|
||||
testUtils.testWithClient('without optional options', async client => {
|
||||
await Promise.all([
|
||||
client.ft.create('index', {
|
||||
field: SchemaFieldTypes.NUMERIC
|
||||
}),
|
||||
client.hSet('1', 'field', '1')
|
||||
]);
|
||||
|
||||
assert.deepEqual(
|
||||
await client.ft.search('index', '*'),
|
||||
{
|
||||
total: 1,
|
||||
documents: [{
|
||||
id: '1',
|
||||
value: Object.create(null, {
|
||||
field: {
|
||||
value: '1',
|
||||
configurable: true,
|
||||
enumerable: true
|
||||
}
|
||||
})
|
||||
}]
|
||||
}
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
|
||||
testUtils.testWithClient('RETURN []', async client => {
|
||||
await Promise.all([
|
||||
client.ft.create('index', {
|
||||
field: SchemaFieldTypes.NUMERIC
|
||||
}),
|
||||
client.hSet('1', 'field', '1'),
|
||||
client.hSet('2', 'field', '2')
|
||||
]);
|
||||
|
||||
assert.deepEqual(
|
||||
await client.ft.search('index', '*', {
|
||||
RETURN: []
|
||||
}),
|
||||
{
|
||||
total: 2,
|
||||
documents: [{
|
||||
id: '1',
|
||||
value: Object.create(null)
|
||||
}, {
|
||||
id: '2',
|
||||
value: Object.create(null)
|
||||
}]
|
||||
}
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
it('with VERBATIM', () => {
|
||||
assert.deepEqual(
|
||||
SEARCH.transformArguments('index', 'query', {
|
||||
VERBATIM: true
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'VERBATIM']
|
||||
);
|
||||
});
|
||||
|
||||
it('with NOSTOPWORDS', () => {
|
||||
assert.deepEqual(
|
||||
SEARCH.transformArguments('index', 'query', {
|
||||
NOSTOPWORDS: true
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'NOSTOPWORDS']
|
||||
);
|
||||
});
|
||||
|
||||
it('with INKEYS', () => {
|
||||
assert.deepEqual(
|
||||
SEARCH.transformArguments('index', 'query', {
|
||||
INKEYS: 'key'
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'INKEYS', '1', 'key']
|
||||
);
|
||||
});
|
||||
|
||||
it('with INFIELDS', () => {
|
||||
assert.deepEqual(
|
||||
SEARCH.transformArguments('index', 'query', {
|
||||
INFIELDS: 'field'
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'INFIELDS', '1', 'field']
|
||||
);
|
||||
});
|
||||
|
||||
it('with RETURN', () => {
|
||||
assert.deepEqual(
|
||||
SEARCH.transformArguments('index', 'query', {
|
||||
RETURN: 'return'
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'RETURN', '1', 'return']
|
||||
);
|
||||
});
|
||||
|
||||
describe('with SUMMARIZE', () => {
|
||||
it('true', () => {
|
||||
assert.deepEqual(
|
||||
SEARCH.transformArguments('index', 'query', {
|
||||
SUMMARIZE: true
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'SUMMARIZE']
|
||||
);
|
||||
});
|
||||
|
||||
describe('with FIELDS', () => {
|
||||
it('string', () => {
|
||||
assert.deepEqual(
|
||||
SEARCH.transformArguments('index', 'query', {
|
||||
SUMMARIZE: {
|
||||
FIELDS: '@field'
|
||||
}
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'SUMMARIZE', 'FIELDS', '1', '@field']
|
||||
);
|
||||
});
|
||||
|
||||
it('Array', () => {
|
||||
assert.deepEqual(
|
||||
SEARCH.transformArguments('index', 'query', {
|
||||
SUMMARIZE: {
|
||||
FIELDS: ['@1', '@2']
|
||||
}
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'SUMMARIZE', 'FIELDS', '2', '@1', '@2']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('with FRAGS', () => {
|
||||
assert.deepEqual(
|
||||
SEARCH.transformArguments('index', 'query', {
|
||||
SUMMARIZE: {
|
||||
FRAGS: 1
|
||||
}
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'SUMMARIZE', 'FRAGS', '1']
|
||||
);
|
||||
});
|
||||
|
||||
it('with LEN', () => {
|
||||
assert.deepEqual(
|
||||
SEARCH.transformArguments('index', 'query', {
|
||||
SUMMARIZE: {
|
||||
LEN: 1
|
||||
}
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'SUMMARIZE', 'LEN', '1']
|
||||
);
|
||||
});
|
||||
|
||||
it('with SEPARATOR', () => {
|
||||
assert.deepEqual(
|
||||
SEARCH.transformArguments('index', 'query', {
|
||||
SUMMARIZE: {
|
||||
SEPARATOR: 'separator'
|
||||
}
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'SUMMARIZE', 'SEPARATOR', 'separator']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with HIGHLIGHT', () => {
|
||||
it('true', () => {
|
||||
assert.deepEqual(
|
||||
SEARCH.transformArguments('index', 'query', {
|
||||
HIGHLIGHT: true
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'HIGHLIGHT']
|
||||
);
|
||||
});
|
||||
|
||||
describe('with FIELDS', () => {
|
||||
it('string', () => {
|
||||
assert.deepEqual(
|
||||
SEARCH.transformArguments('index', 'query', {
|
||||
HIGHLIGHT: {
|
||||
FIELDS: ['@field']
|
||||
}
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'HIGHLIGHT', 'FIELDS', '1', '@field']
|
||||
);
|
||||
});
|
||||
|
||||
it('Array', () => {
|
||||
assert.deepEqual(
|
||||
SEARCH.transformArguments('index', 'query', {
|
||||
HIGHLIGHT: {
|
||||
FIELDS: ['@1', '@2']
|
||||
}
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'HIGHLIGHT', 'FIELDS', '2', '@1', '@2']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('with TAGS', () => {
|
||||
assert.deepEqual(
|
||||
SEARCH.transformArguments('index', 'query', {
|
||||
HIGHLIGHT: {
|
||||
TAGS: {
|
||||
open: 'open',
|
||||
close: 'close'
|
||||
}
|
||||
}
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'HIGHLIGHT', 'TAGS', 'open', 'close']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('with SLOP', () => {
|
||||
assert.deepEqual(
|
||||
SEARCH.transformArguments('index', 'query', {
|
||||
SLOP: 1
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'SLOP', '1']
|
||||
);
|
||||
});
|
||||
|
||||
it('with TIMEOUT', () => {
|
||||
assert.deepEqual(
|
||||
SEARCH.transformArguments('index', 'query', {
|
||||
TIMEOUT: 1
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'TIMEOUT', '1']
|
||||
);
|
||||
});
|
||||
|
||||
it('with INORDER', () => {
|
||||
assert.deepEqual(
|
||||
SEARCH.transformArguments('index', 'query', {
|
||||
INORDER: true
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'INORDER']
|
||||
);
|
||||
});
|
||||
|
||||
it('with LANGUAGE', () => {
|
||||
assert.deepEqual(
|
||||
SEARCH.transformArguments('index', 'query', {
|
||||
LANGUAGE: 'Arabic'
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'LANGUAGE', 'Arabic']
|
||||
);
|
||||
});
|
||||
|
||||
it('with EXPANDER', () => {
|
||||
assert.deepEqual(
|
||||
SEARCH.transformArguments('index', 'query', {
|
||||
EXPANDER: 'expender'
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'EXPANDER', 'expender']
|
||||
);
|
||||
});
|
||||
|
||||
it('with SCORER', () => {
|
||||
assert.deepEqual(
|
||||
SEARCH.transformArguments('index', 'query', {
|
||||
SCORER: 'scorer'
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'SCORER', 'scorer']
|
||||
);
|
||||
});
|
||||
|
||||
it('with SORTBY', () => {
|
||||
assert.deepEqual(
|
||||
SEARCH.transformArguments('index', 'query', {
|
||||
SORTBY: '@by'
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'SORTBY', '@by']
|
||||
);
|
||||
});
|
||||
|
||||
it('with LIMIT', () => {
|
||||
assert.deepEqual(
|
||||
SEARCH.transformArguments('index', 'query', {
|
||||
LIMIT: {
|
||||
from: 0,
|
||||
size: 1
|
||||
}
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'LIMIT', '0', '1']
|
||||
);
|
||||
});
|
||||
|
||||
it('with PARAMS', () => {
|
||||
assert.deepEqual(
|
||||
SEARCH.transformArguments('index', 'query', {
|
||||
PARAMS: {
|
||||
string: 'string',
|
||||
buffer: Buffer.from('buffer'),
|
||||
number: 1
|
||||
}
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'PARAMS', '6', 'string', 'string', 'buffer', Buffer.from('buffer'), 'number', '1']
|
||||
);
|
||||
});
|
||||
|
||||
it('with DIALECT', () => {
|
||||
assert.deepEqual(
|
||||
SEARCH.transformArguments('index', 'query', {
|
||||
DIALECT: 1
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'DIALECT', '1']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('client.ft.search', () => {
|
||||
testUtils.testWithClient('without optional options', async client => {
|
||||
await Promise.all([
|
||||
client.ft.create('index', {
|
||||
field: 'TEXT'
|
||||
}),
|
||||
client.hSet('1', 'field', '1')
|
||||
]);
|
||||
|
||||
assert.deepEqual(
|
||||
await client.ft.search('index', '*'),
|
||||
{
|
||||
total: 1,
|
||||
documents: [{
|
||||
id: '1',
|
||||
value: Object.create(null, {
|
||||
field: {
|
||||
value: '1',
|
||||
configurable: true,
|
||||
enumerable: true
|
||||
}
|
||||
})
|
||||
}]
|
||||
}
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
|
||||
testUtils.testWithClient('RETURN []', async client => {
|
||||
await Promise.all([
|
||||
client.ft.create('index', {
|
||||
field: 'TEXT'
|
||||
}),
|
||||
client.hSet('1', 'field', '1'),
|
||||
client.hSet('2', 'field', '2')
|
||||
]);
|
||||
|
||||
assert.deepEqual(
|
||||
await client.ft.search('index', '*', {
|
||||
RETURN: []
|
||||
}),
|
||||
{
|
||||
total: 2,
|
||||
documents: [{
|
||||
id: '1',
|
||||
value: Object.create(null)
|
||||
}, {
|
||||
id: '2',
|
||||
value: Object.create(null)
|
||||
}]
|
||||
}
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
||||
});
|
||||
|
@@ -1,109 +1,222 @@
|
||||
import { RedisCommandArguments } from '@redis/client/dist/lib/commands';
|
||||
import { pushSearchOptions, RedisSearchLanguages, Params, PropertyName, SortByProperty, SearchReply } from '.';
|
||||
import { RedisArgument, Command, ReplyUnion } from '@redis/client/dist/lib/RESP/types';
|
||||
import { RedisVariadicArgument, pushOptionalVariadicArgument } from '@redis/client/dist/lib/commands/generic-transformers';
|
||||
import { RediSearchProperty, RediSearchLanguage } from './CREATE';
|
||||
|
||||
export const FIRST_KEY_INDEX = 1;
|
||||
export type FtSearchParams = Record<string, RedisArgument | number>;
|
||||
|
||||
export const IS_READ_ONLY = true;
|
||||
export function pushParamsArgument(args: Array<RedisArgument>, params?: FtSearchParams) {
|
||||
if (params) {
|
||||
const length = args.push('PARAMS', '');
|
||||
for (const key in params) {
|
||||
if (!Object.hasOwn(params, key)) continue;
|
||||
|
||||
export interface SearchOptions {
|
||||
VERBATIM?: true;
|
||||
NOSTOPWORDS?: true;
|
||||
// WITHSCORES?: true;
|
||||
// WITHPAYLOADS?: true;
|
||||
WITHSORTKEYS?: true;
|
||||
// FILTER?: {
|
||||
// field: string;
|
||||
// min: number | string;
|
||||
// max: number | string;
|
||||
// };
|
||||
// GEOFILTER?: {
|
||||
// field: string;
|
||||
// lon: number;
|
||||
// lat: number;
|
||||
// radius: number;
|
||||
// unit: 'm' | 'km' | 'mi' | 'ft';
|
||||
// };
|
||||
INKEYS?: string | Array<string>;
|
||||
INFIELDS?: string | Array<string>;
|
||||
RETURN?: string | Array<string>;
|
||||
SUMMARIZE?: true | {
|
||||
FIELDS?: PropertyName | Array<PropertyName>;
|
||||
FRAGS?: number;
|
||||
LEN?: number;
|
||||
SEPARATOR?: string;
|
||||
};
|
||||
HIGHLIGHT?: true | {
|
||||
FIELDS?: PropertyName | Array<PropertyName>;
|
||||
TAGS?: {
|
||||
open: string;
|
||||
close: string;
|
||||
};
|
||||
};
|
||||
SLOP?: number;
|
||||
INORDER?: true;
|
||||
LANGUAGE?: RedisSearchLanguages;
|
||||
EXPANDER?: string;
|
||||
SCORER?: string;
|
||||
// EXPLAINSCORE?: true; // TODO: WITHSCORES
|
||||
// PAYLOAD?: ;
|
||||
SORTBY?: SortByProperty;
|
||||
// MSORTBY?: SortByProperty | Array<SortByProperty>;
|
||||
LIMIT?: {
|
||||
from: number | string;
|
||||
size: number | string;
|
||||
};
|
||||
PARAMS?: Params;
|
||||
DIALECT?: number;
|
||||
TIMEOUT?: number;
|
||||
const value = params[key];
|
||||
args.push(
|
||||
key,
|
||||
typeof value === 'number' ? value.toString() : value
|
||||
);
|
||||
}
|
||||
|
||||
args[length - 1] = (args.length - length).toString();
|
||||
}
|
||||
}
|
||||
|
||||
export function transformArguments(
|
||||
index: string,
|
||||
query: string,
|
||||
options?: SearchOptions
|
||||
): RedisCommandArguments {
|
||||
return pushSearchOptions(
|
||||
['FT.SEARCH', index, query],
|
||||
options
|
||||
);
|
||||
export interface FtSearchOptions {
|
||||
VERBATIM?: boolean;
|
||||
NOSTOPWORDS?: boolean;
|
||||
INKEYS?: RedisVariadicArgument;
|
||||
INFIELDS?: RedisVariadicArgument;
|
||||
RETURN?: RedisVariadicArgument;
|
||||
SUMMARIZE?: boolean | {
|
||||
FIELDS?: RediSearchProperty | Array<RediSearchProperty>;
|
||||
FRAGS?: number;
|
||||
LEN?: number;
|
||||
SEPARATOR?: RedisArgument;
|
||||
};
|
||||
HIGHLIGHT?: boolean | {
|
||||
FIELDS?: RediSearchProperty | Array<RediSearchProperty>;
|
||||
TAGS?: {
|
||||
open: RedisArgument;
|
||||
close: RedisArgument;
|
||||
};
|
||||
};
|
||||
SLOP?: number;
|
||||
TIMEOUT?: number;
|
||||
INORDER?: boolean;
|
||||
LANGUAGE?: RediSearchLanguage;
|
||||
EXPANDER?: RedisArgument;
|
||||
SCORER?: RedisArgument;
|
||||
SORTBY?: RedisArgument | {
|
||||
BY: RediSearchProperty;
|
||||
DIRECTION?: 'ASC' | 'DESC';
|
||||
};
|
||||
LIMIT?: {
|
||||
from: number | RedisArgument;
|
||||
size: number | RedisArgument;
|
||||
};
|
||||
PARAMS?: FtSearchParams;
|
||||
DIALECT?: number;
|
||||
}
|
||||
|
||||
export function pushSearchOptions(args: Array<RedisArgument>, options?: FtSearchOptions) {
|
||||
if (options?.VERBATIM) {
|
||||
args.push('VERBATIM');
|
||||
}
|
||||
|
||||
if (options?.NOSTOPWORDS) {
|
||||
args.push('NOSTOPWORDS');
|
||||
}
|
||||
|
||||
pushOptionalVariadicArgument(args, 'INKEYS', options?.INKEYS);
|
||||
pushOptionalVariadicArgument(args, 'INFIELDS', options?.INFIELDS);
|
||||
pushOptionalVariadicArgument(args, 'RETURN', options?.RETURN);
|
||||
|
||||
if (options?.SUMMARIZE) {
|
||||
args.push('SUMMARIZE');
|
||||
|
||||
if (typeof options.SUMMARIZE === 'object') {
|
||||
pushOptionalVariadicArgument(args, 'FIELDS', options.SUMMARIZE.FIELDS);
|
||||
|
||||
if (options.SUMMARIZE.FRAGS !== undefined) {
|
||||
args.push('FRAGS', options.SUMMARIZE.FRAGS.toString());
|
||||
}
|
||||
|
||||
if (options.SUMMARIZE.LEN !== undefined) {
|
||||
args.push('LEN', options.SUMMARIZE.LEN.toString());
|
||||
}
|
||||
|
||||
if (options.SUMMARIZE.SEPARATOR !== undefined) {
|
||||
args.push('SEPARATOR', options.SUMMARIZE.SEPARATOR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (options?.HIGHLIGHT) {
|
||||
args.push('HIGHLIGHT');
|
||||
|
||||
if (typeof options.HIGHLIGHT === 'object') {
|
||||
pushOptionalVariadicArgument(args, 'FIELDS', options.HIGHLIGHT.FIELDS);
|
||||
|
||||
if (options.HIGHLIGHT.TAGS) {
|
||||
args.push('TAGS', options.HIGHLIGHT.TAGS.open, options.HIGHLIGHT.TAGS.close);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (options?.SLOP !== undefined) {
|
||||
args.push('SLOP', options.SLOP.toString());
|
||||
}
|
||||
|
||||
if (options?.TIMEOUT !== undefined) {
|
||||
args.push('TIMEOUT', options.TIMEOUT.toString());
|
||||
}
|
||||
|
||||
if (options?.INORDER) {
|
||||
args.push('INORDER');
|
||||
}
|
||||
|
||||
if (options?.LANGUAGE) {
|
||||
args.push('LANGUAGE', options.LANGUAGE);
|
||||
}
|
||||
|
||||
if (options?.EXPANDER) {
|
||||
args.push('EXPANDER', options.EXPANDER);
|
||||
}
|
||||
|
||||
if (options?.SCORER) {
|
||||
args.push('SCORER', options.SCORER);
|
||||
}
|
||||
|
||||
if (options?.SORTBY) {
|
||||
args.push('SORTBY');
|
||||
|
||||
if (typeof options.SORTBY === 'string' || options.SORTBY instanceof Buffer) {
|
||||
args.push(options.SORTBY);
|
||||
} else {
|
||||
args.push(options.SORTBY.BY);
|
||||
|
||||
if (options.SORTBY.DIRECTION) {
|
||||
args.push(options.SORTBY.DIRECTION);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (options?.LIMIT) {
|
||||
args.push('LIMIT', options.LIMIT.from.toString(), options.LIMIT.size.toString());
|
||||
}
|
||||
|
||||
pushParamsArgument(args, options?.PARAMS);
|
||||
|
||||
if (options?.DIALECT !== undefined) {
|
||||
args.push('DIALECT', options.DIALECT.toString());
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
export default {
|
||||
FIRST_KEY_INDEX: undefined,
|
||||
IS_READ_ONLY: true,
|
||||
transformArguments(index: RedisArgument, query: RedisArgument, options?: FtSearchOptions) {
|
||||
const args = ['FT.SEARCH', index, query];
|
||||
|
||||
return pushSearchOptions(args, options);
|
||||
},
|
||||
transformReply: {
|
||||
2: (reply: SearchRawReply): SearchReply => {
|
||||
const withoutDocuments = (reply[0] + 1 == reply.length)
|
||||
|
||||
const documents = [];
|
||||
let i = 1;
|
||||
while (i < reply.length) {
|
||||
documents.push({
|
||||
id: reply[i++],
|
||||
value: withoutDocuments ? Object.create(null) : documentValue(reply[i++])
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
total: reply[0],
|
||||
documents
|
||||
};
|
||||
},
|
||||
3: undefined as unknown as () => ReplyUnion
|
||||
},
|
||||
unstableResp3: true
|
||||
} as const satisfies Command;
|
||||
|
||||
export type SearchRawReply = Array<any>;
|
||||
|
||||
export function transformReply(reply: SearchRawReply, withoutDocuments: boolean): SearchReply {
|
||||
const documents = [];
|
||||
let i = 1;
|
||||
while (i < reply.length) {
|
||||
documents.push({
|
||||
id: reply[i++],
|
||||
value: withoutDocuments ? Object.create(null) : documentValue(reply[i++])
|
||||
});
|
||||
}
|
||||
interface SearchDocumentValue {
|
||||
[key: string]: string | number | null | Array<SearchDocumentValue> | SearchDocumentValue;
|
||||
}
|
||||
|
||||
return {
|
||||
total: reply[0],
|
||||
documents
|
||||
};
|
||||
export interface SearchReply {
|
||||
total: number;
|
||||
documents: Array<{
|
||||
id: string;
|
||||
value: SearchDocumentValue;
|
||||
}>;
|
||||
}
|
||||
|
||||
function documentValue(tuples: any) {
|
||||
const message = Object.create(null);
|
||||
const message = Object.create(null);
|
||||
|
||||
let i = 0;
|
||||
while (i < tuples.length) {
|
||||
const key = tuples[i++],
|
||||
value = tuples[i++];
|
||||
if (key === '$') { // might be a JSON reply
|
||||
try {
|
||||
Object.assign(message, JSON.parse(value));
|
||||
continue;
|
||||
} catch {
|
||||
// set as a regular property if not a valid JSON
|
||||
}
|
||||
}
|
||||
let i = 0;
|
||||
while (i < tuples.length) {
|
||||
const key = tuples[i++],
|
||||
value = tuples[i++];
|
||||
if (key === '$') { // might be a JSON reply
|
||||
try {
|
||||
Object.assign(message, JSON.parse(value));
|
||||
continue;
|
||||
} catch {
|
||||
// set as a regular property if not a valid JSON
|
||||
}
|
||||
}
|
||||
|
||||
message[key] = value;
|
||||
}
|
||||
message[key] = value;
|
||||
}
|
||||
|
||||
return message;
|
||||
return message;
|
||||
}
|
||||
|
@@ -1,45 +1,34 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import { SchemaFieldTypes } from '.';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { transformArguments, transformReply } from './SEARCH_NOCONTENT';
|
||||
import SEARCH_NOCONTENT from './SEARCH_NOCONTENT';
|
||||
|
||||
describe('SEARCH_NOCONTENT', () => {
|
||||
describe('transformArguments', () => {
|
||||
it('without options', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query'),
|
||||
['FT.SEARCH', 'index', 'query', 'NOCONTENT']
|
||||
);
|
||||
});
|
||||
describe('FT.SEARCH NOCONTENT', () => {
|
||||
describe('transformArguments', () => {
|
||||
it('without options', () => {
|
||||
assert.deepEqual(
|
||||
SEARCH_NOCONTENT.transformArguments('index', 'query'),
|
||||
['FT.SEARCH', 'index', 'query', 'NOCONTENT']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('transformReply', () => {
|
||||
it('returns total and keys', () => {
|
||||
assert.deepEqual(transformReply([3, '1', '2', '3']), {
|
||||
total: 3,
|
||||
documents: ['1', '2', '3']
|
||||
})
|
||||
});
|
||||
});
|
||||
describe('client.ft.searchNoContent', () => {
|
||||
testUtils.testWithClient('returns total and keys', async client => {
|
||||
await Promise.all([
|
||||
client.ft.create('index', {
|
||||
field: 'TEXT'
|
||||
}),
|
||||
client.hSet('1', 'field', 'field1'),
|
||||
client.hSet('2', 'field', 'field2')
|
||||
]);
|
||||
|
||||
describe('client.ft.searchNoContent', () => {
|
||||
testUtils.testWithClient('returns total and keys', async client => {
|
||||
await Promise.all([
|
||||
client.ft.create('index', {
|
||||
field: SchemaFieldTypes.TEXT
|
||||
}),
|
||||
client.hSet('1', 'field', 'field1'),
|
||||
client.hSet('2', 'field', 'field2'),
|
||||
client.hSet('3', 'field', 'field3')
|
||||
]);
|
||||
|
||||
assert.deepEqual(
|
||||
await client.ft.searchNoContent('index', '*'),
|
||||
{
|
||||
total: 3,
|
||||
documents: ['1','2','3']
|
||||
}
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
||||
assert.deepEqual(
|
||||
await client.ft.searchNoContent('index', '*'),
|
||||
{
|
||||
total: 2,
|
||||
documents: ['1', '2']
|
||||
}
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
||||
});
|
||||
|
@@ -1,30 +1,27 @@
|
||||
import { RedisCommandArguments } from "@redis/client/dist/lib/commands";
|
||||
import { pushSearchOptions } from ".";
|
||||
import { SearchOptions, SearchRawReply } from "./SEARCH";
|
||||
import { Command, ReplyUnion } from '@redis/client/dist/lib/RESP/types';
|
||||
import SEARCH, { SearchRawReply } from './SEARCH';
|
||||
|
||||
export const FIRST_KEY_INDEX = 1;
|
||||
|
||||
export const IS_READ_ONLY = true;
|
||||
|
||||
export function transformArguments(
|
||||
index: string,
|
||||
query: string,
|
||||
options?: SearchOptions
|
||||
): RedisCommandArguments {
|
||||
return pushSearchOptions(
|
||||
['FT.SEARCH', index, query, 'NOCONTENT'],
|
||||
options
|
||||
);
|
||||
}
|
||||
|
||||
export interface SearchNoContentReply {
|
||||
total: number;
|
||||
documents: Array<string>;
|
||||
};
|
||||
|
||||
export function transformReply(reply: SearchRawReply): SearchNoContentReply {
|
||||
return {
|
||||
export default {
|
||||
FIRST_KEY_INDEX: SEARCH.FIRST_KEY_INDEX,
|
||||
IS_READ_ONLY: SEARCH.IS_READ_ONLY,
|
||||
transformArguments(...args: Parameters<typeof SEARCH.transformArguments>) {
|
||||
const redisArgs = SEARCH.transformArguments(...args);
|
||||
redisArgs.push('NOCONTENT');
|
||||
return redisArgs;
|
||||
},
|
||||
transformReply: {
|
||||
2: (reply: SearchRawReply): SearchNoContentReply => {
|
||||
return {
|
||||
total: reply[0],
|
||||
documents: reply.slice(1)
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
3: undefined as unknown as () => ReplyUnion
|
||||
},
|
||||
unstableResp3: true
|
||||
} as const satisfies Command;
|
||||
|
||||
export interface SearchNoContentReply {
|
||||
total: number;
|
||||
documents: Array<string>;
|
||||
};
|
@@ -1,80 +1,79 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { SchemaFieldTypes } from '.';
|
||||
import { transformArguments } from './SPELLCHECK';
|
||||
import SPELLCHECK from './SPELLCHECK';
|
||||
|
||||
describe('SPELLCHECK', () => {
|
||||
describe('transformArguments', () => {
|
||||
it('without options', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query'),
|
||||
['FT.SPELLCHECK', 'index', 'query']
|
||||
);
|
||||
});
|
||||
|
||||
it('with DISTANCE', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', { DISTANCE: 2 }),
|
||||
['FT.SPELLCHECK', 'index', 'query', 'DISTANCE', '2']
|
||||
);
|
||||
});
|
||||
|
||||
describe('with TERMS', () => {
|
||||
it('single', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', {
|
||||
TERMS: {
|
||||
mode: 'INCLUDE',
|
||||
dictionary: 'dictionary'
|
||||
}
|
||||
}),
|
||||
['FT.SPELLCHECK', 'index', 'query', 'TERMS', 'INCLUDE', 'dictionary']
|
||||
);
|
||||
});
|
||||
|
||||
it('multiple', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', {
|
||||
TERMS: [{
|
||||
mode: 'INCLUDE',
|
||||
dictionary: 'include'
|
||||
}, {
|
||||
mode: 'EXCLUDE',
|
||||
dictionary: 'exclude'
|
||||
}]
|
||||
}),
|
||||
['FT.SPELLCHECK', 'index', 'query', 'TERMS', 'INCLUDE', 'include', 'TERMS', 'EXCLUDE', 'exclude']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('with DIALECT', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', {
|
||||
DIALECT: 1
|
||||
}),
|
||||
['FT.SPELLCHECK', 'index', 'query', 'DIALECT', '1']
|
||||
);
|
||||
});
|
||||
describe('FT.SPELLCHECK', () => {
|
||||
describe('transformArguments', () => {
|
||||
it('without options', () => {
|
||||
assert.deepEqual(
|
||||
SPELLCHECK.transformArguments('index', 'query'),
|
||||
['FT.SPELLCHECK', 'index', 'query']
|
||||
);
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.spellCheck', async client => {
|
||||
await Promise.all([
|
||||
client.ft.create('index', {
|
||||
field: SchemaFieldTypes.TEXT
|
||||
}),
|
||||
client.hSet('key', 'field', 'query')
|
||||
]);
|
||||
it('with DISTANCE', () => {
|
||||
assert.deepEqual(
|
||||
SPELLCHECK.transformArguments('index', 'query', {
|
||||
DISTANCE: 2
|
||||
}),
|
||||
['FT.SPELLCHECK', 'index', 'query', 'DISTANCE', '2']
|
||||
);
|
||||
});
|
||||
|
||||
describe('with TERMS', () => {
|
||||
it('single', () => {
|
||||
assert.deepEqual(
|
||||
await client.ft.spellCheck('index', 'quer'),
|
||||
[{
|
||||
term: 'quer',
|
||||
suggestions: [{
|
||||
score: 1,
|
||||
suggestion: 'query'
|
||||
}]
|
||||
}]
|
||||
SPELLCHECK.transformArguments('index', 'query', {
|
||||
TERMS: {
|
||||
mode: 'INCLUDE',
|
||||
dictionary: 'dictionary'
|
||||
}
|
||||
}),
|
||||
['FT.SPELLCHECK', 'index', 'query', 'TERMS', 'INCLUDE', 'dictionary']
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
||||
|
||||
it('multiple', () => {
|
||||
assert.deepEqual(
|
||||
SPELLCHECK.transformArguments('index', 'query', {
|
||||
TERMS: [{
|
||||
mode: 'INCLUDE',
|
||||
dictionary: 'include'
|
||||
}, {
|
||||
mode: 'EXCLUDE',
|
||||
dictionary: 'exclude'
|
||||
}]
|
||||
}),
|
||||
['FT.SPELLCHECK', 'index', 'query', 'TERMS', 'INCLUDE', 'include', 'TERMS', 'EXCLUDE', 'exclude']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('with DIALECT', () => {
|
||||
assert.deepEqual(
|
||||
SPELLCHECK.transformArguments('index', 'query', {
|
||||
DIALECT: 1
|
||||
}),
|
||||
['FT.SPELLCHECK', 'index', 'query', 'DIALECT', '1']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.spellCheck', async client => {
|
||||
const [,, reply] = await Promise.all([
|
||||
client.ft.create('index', {
|
||||
field: 'TEXT'
|
||||
}),
|
||||
client.hSet('key', 'field', 'query'),
|
||||
client.ft.spellCheck('index', 'quer')
|
||||
]);
|
||||
|
||||
assert.deepEqual(reply, [{
|
||||
term: 'quer',
|
||||
suggestions: [{
|
||||
score: 1,
|
||||
suggestion: 'query'
|
||||
}]
|
||||
}]);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
||||
|
@@ -1,62 +1,71 @@
|
||||
interface SpellCheckTerms {
|
||||
mode: 'INCLUDE' | 'EXCLUDE';
|
||||
dictionary: string;
|
||||
import { RedisArgument, CommandArguments, Command, ReplyUnion } from '@redis/client/dist/lib/RESP/types';
|
||||
|
||||
export interface Terms {
|
||||
mode: 'INCLUDE' | 'EXCLUDE';
|
||||
dictionary: RedisArgument;
|
||||
}
|
||||
|
||||
interface SpellCheckOptions {
|
||||
DISTANCE?: number;
|
||||
TERMS?: SpellCheckTerms | Array<SpellCheckTerms>;
|
||||
DIALECT?: number;
|
||||
export interface FtSpellCheckOptions {
|
||||
DISTANCE?: number;
|
||||
TERMS?: Terms | Array<Terms>;
|
||||
DIALECT?: number;
|
||||
}
|
||||
|
||||
export function transformArguments(index: string, query: string, options?: SpellCheckOptions): Array<string> {
|
||||
export default {
|
||||
FIRST_KEY_INDEX: undefined,
|
||||
IS_READ_ONLY: true,
|
||||
transformArguments(index: RedisArgument, query: RedisArgument, options?: FtSpellCheckOptions) {
|
||||
const args = ['FT.SPELLCHECK', index, query];
|
||||
|
||||
if (options?.DISTANCE) {
|
||||
args.push('DISTANCE', options.DISTANCE.toString());
|
||||
args.push('DISTANCE', options.DISTANCE.toString());
|
||||
}
|
||||
|
||||
if (options?.TERMS) {
|
||||
if (Array.isArray(options.TERMS)) {
|
||||
for (const term of options.TERMS) {
|
||||
pushTerms(args, term);
|
||||
}
|
||||
} else {
|
||||
pushTerms(args, options.TERMS);
|
||||
if (Array.isArray(options.TERMS)) {
|
||||
for (const term of options.TERMS) {
|
||||
pushTerms(args, term);
|
||||
}
|
||||
} else {
|
||||
pushTerms(args, options.TERMS);
|
||||
}
|
||||
}
|
||||
|
||||
if (options?.DIALECT) {
|
||||
args.push('DIALECT', options.DIALECT.toString());
|
||||
args.push('DIALECT', options.DIALECT.toString());
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
function pushTerms(args: Array<string>, { mode, dictionary }: SpellCheckTerms): void {
|
||||
args.push('TERMS', mode, dictionary);
|
||||
}
|
||||
},
|
||||
transformReply: {
|
||||
2: (rawReply: SpellCheckRawReply): SpellCheckReply => {
|
||||
return rawReply.map(([, term, suggestions]) => ({
|
||||
term,
|
||||
suggestions: suggestions.map(([score, suggestion]) => ({
|
||||
score: Number(score),
|
||||
suggestion
|
||||
}))
|
||||
}));
|
||||
},
|
||||
3: undefined as unknown as () => ReplyUnion,
|
||||
},
|
||||
unstableResp3: true
|
||||
} as const satisfies Command;
|
||||
|
||||
type SpellCheckRawReply = Array<[
|
||||
_: string,
|
||||
term: string,
|
||||
suggestions: Array<[score: string, suggestion: string]>
|
||||
_: string,
|
||||
term: string,
|
||||
suggestions: Array<[score: string, suggestion: string]>
|
||||
]>;
|
||||
|
||||
type SpellCheckReply = Array<{
|
||||
term: string,
|
||||
suggestions: Array<{
|
||||
score: number,
|
||||
suggestion: string
|
||||
}>
|
||||
term: string,
|
||||
suggestions: Array<{
|
||||
score: number,
|
||||
suggestion: string
|
||||
}>
|
||||
}>;
|
||||
|
||||
export function transformReply(rawReply: SpellCheckRawReply): SpellCheckReply {
|
||||
return rawReply.map(([, term, suggestions]) => ({
|
||||
term,
|
||||
suggestions: suggestions.map(([score, suggestion]) => ({
|
||||
score: Number(score),
|
||||
suggestion
|
||||
}))
|
||||
}));
|
||||
function pushTerms(args: CommandArguments, { mode, dictionary }: Terms) {
|
||||
args.push('TERMS', mode, dictionary);
|
||||
}
|
||||
|
@@ -1,35 +1,35 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { transformArguments } from './SUGADD';
|
||||
import SUGADD from './SUGADD';
|
||||
|
||||
describe('SUGADD', () => {
|
||||
describe('transformArguments', () => {
|
||||
it('without options', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('key', 'string', 1),
|
||||
['FT.SUGADD', 'key', 'string', '1']
|
||||
);
|
||||
});
|
||||
|
||||
it('with INCR', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('key', 'string', 1, { INCR: true }),
|
||||
['FT.SUGADD', 'key', 'string', '1', 'INCR']
|
||||
);
|
||||
});
|
||||
|
||||
it('with PAYLOAD', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('key', 'string', 1, { PAYLOAD: 'payload' }),
|
||||
['FT.SUGADD', 'key', 'string', '1', 'PAYLOAD', 'payload']
|
||||
);
|
||||
});
|
||||
describe('FT.SUGADD', () => {
|
||||
describe('transformArguments', () => {
|
||||
it('without options', () => {
|
||||
assert.deepEqual(
|
||||
SUGADD.transformArguments('key', 'string', 1),
|
||||
['FT.SUGADD', 'key', 'string', '1']
|
||||
);
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.sugAdd', async client => {
|
||||
assert.equal(
|
||||
await client.ft.sugAdd('key', 'string', 1),
|
||||
1
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
it('with INCR', () => {
|
||||
assert.deepEqual(
|
||||
SUGADD.transformArguments('key', 'string', 1, { INCR: true }),
|
||||
['FT.SUGADD', 'key', 'string', '1', 'INCR']
|
||||
);
|
||||
});
|
||||
|
||||
it('with PAYLOAD', () => {
|
||||
assert.deepEqual(
|
||||
SUGADD.transformArguments('key', 'string', 1, { PAYLOAD: 'payload' }),
|
||||
['FT.SUGADD', 'key', 'string', '1', 'PAYLOAD', 'payload']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.sugAdd', async client => {
|
||||
assert.equal(
|
||||
await client.ft.sugAdd('key', 'string', 1),
|
||||
1
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
||||
|
@@ -1,20 +1,25 @@
|
||||
interface SugAddOptions {
|
||||
INCR?: true;
|
||||
PAYLOAD?: string;
|
||||
import { RedisArgument, NumberReply, Command } from '@redis/client/dist/lib/RESP/types';
|
||||
|
||||
export interface FtSugAddOptions {
|
||||
INCR?: boolean;
|
||||
PAYLOAD?: RedisArgument;
|
||||
}
|
||||
|
||||
export function transformArguments(key: string, string: string, score: number, options?: SugAddOptions): Array<string> {
|
||||
export default {
|
||||
FIRST_KEY_INDEX: 1,
|
||||
IS_READ_ONLY: true,
|
||||
transformArguments(key: RedisArgument, string: RedisArgument, score: number, options?: FtSugAddOptions) {
|
||||
const args = ['FT.SUGADD', key, string, score.toString()];
|
||||
|
||||
if (options?.INCR) {
|
||||
args.push('INCR');
|
||||
args.push('INCR');
|
||||
}
|
||||
|
||||
if (options?.PAYLOAD) {
|
||||
args.push('PAYLOAD', options.PAYLOAD);
|
||||
args.push('PAYLOAD', options.PAYLOAD);
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
export declare function transformReply(): number;
|
||||
},
|
||||
transformReply: undefined as unknown as () => NumberReply
|
||||
} as const satisfies Command;
|
||||
|
@@ -1,19 +1,19 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { transformArguments } from './SUGDEL';
|
||||
import SUGDEL from './SUGDEL';
|
||||
|
||||
describe('SUGDEL', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('key', 'string'),
|
||||
['FT.SUGDEL', 'key', 'string']
|
||||
);
|
||||
});
|
||||
describe('FT.SUGDEL', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
SUGDEL.transformArguments('key', 'string'),
|
||||
['FT.SUGDEL', 'key', 'string']
|
||||
);
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.sugDel', async client => {
|
||||
assert.equal(
|
||||
await client.ft.sugDel('key', 'string'),
|
||||
false
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
testUtils.testWithClient('client.ft.sugDel', async client => {
|
||||
assert.equal(
|
||||
await client.ft.sugDel('key', 'string'),
|
||||
0
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
||||
|
@@ -1,5 +1,10 @@
|
||||
export function transformArguments(key: string, string: string): Array<string> {
|
||||
return ['FT.SUGDEL', key, string];
|
||||
}
|
||||
import { RedisArgument, NumberReply, Command } from '@redis/client/dist/lib/RESP/types';
|
||||
|
||||
export { transformBooleanReply as transformReply } from '@redis/client/dist/lib/commands/generic-transformers';
|
||||
export default {
|
||||
FIRST_KEY_INDEX: 1,
|
||||
IS_READ_ONLY: true,
|
||||
transformArguments(key: RedisArgument, string: RedisArgument) {
|
||||
return ['FT.SUGDEL', key, string];
|
||||
},
|
||||
transformReply: undefined as unknown as () => NumberReply<0 | 1>
|
||||
} as const satisfies Command;
|
||||
|
@@ -1,46 +1,46 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { transformArguments } from './SUGGET';
|
||||
import SUGGET from './SUGGET';
|
||||
|
||||
describe('SUGGET', () => {
|
||||
describe('transformArguments', () => {
|
||||
it('without options', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('key', 'prefix'),
|
||||
['FT.SUGGET', 'key', 'prefix']
|
||||
);
|
||||
});
|
||||
|
||||
it('with FUZZY', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('key', 'prefix', { FUZZY: true }),
|
||||
['FT.SUGGET', 'key', 'prefix', 'FUZZY']
|
||||
);
|
||||
});
|
||||
|
||||
it('with MAX', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('key', 'prefix', { MAX: 10 }),
|
||||
['FT.SUGGET', 'key', 'prefix', 'MAX', '10']
|
||||
);
|
||||
});
|
||||
describe('FT.SUGGET', () => {
|
||||
describe('transformArguments', () => {
|
||||
it('without options', () => {
|
||||
assert.deepEqual(
|
||||
SUGGET.transformArguments('key', 'prefix'),
|
||||
['FT.SUGGET', 'key', 'prefix']
|
||||
);
|
||||
});
|
||||
|
||||
describe('client.ft.sugGet', () => {
|
||||
testUtils.testWithClient('null', async client => {
|
||||
assert.equal(
|
||||
await client.ft.sugGet('key', 'prefix'),
|
||||
null
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
|
||||
testUtils.testWithClient('with suggestions', async client => {
|
||||
await client.ft.sugAdd('key', 'string', 1);
|
||||
|
||||
assert.deepEqual(
|
||||
await client.ft.sugGet('key', 'string'),
|
||||
['string']
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
it('with FUZZY', () => {
|
||||
assert.deepEqual(
|
||||
SUGGET.transformArguments('key', 'prefix', { FUZZY: true }),
|
||||
['FT.SUGGET', 'key', 'prefix', 'FUZZY']
|
||||
);
|
||||
});
|
||||
|
||||
it('with MAX', () => {
|
||||
assert.deepEqual(
|
||||
SUGGET.transformArguments('key', 'prefix', { MAX: 10 }),
|
||||
['FT.SUGGET', 'key', 'prefix', 'MAX', '10']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('client.ft.sugGet', () => {
|
||||
testUtils.testWithClient('null', async client => {
|
||||
assert.equal(
|
||||
await client.ft.sugGet('key', 'prefix'),
|
||||
null
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
|
||||
testUtils.testWithClient('with suggestions', async client => {
|
||||
const [, reply] = await Promise.all([
|
||||
client.ft.sugAdd('key', 'string', 1),
|
||||
client.ft.sugGet('key', 's')
|
||||
]);
|
||||
|
||||
assert.deepEqual(reply, ['string']);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
||||
});
|
||||
|
@@ -1,22 +1,25 @@
|
||||
export const IS_READ_ONLY = true;
|
||||
import { NullReply, ArrayReply, BlobStringReply, Command, RedisArgument } from '@redis/client/dist/lib/RESP/types';
|
||||
|
||||
export interface SugGetOptions {
|
||||
FUZZY?: true;
|
||||
MAX?: number;
|
||||
export interface FtSugGetOptions {
|
||||
FUZZY?: boolean;
|
||||
MAX?: number;
|
||||
}
|
||||
|
||||
export function transformArguments(key: string, prefix: string, options?: SugGetOptions): Array<string> {
|
||||
export default {
|
||||
FIRST_KEY_INDEX: 1,
|
||||
IS_READ_ONLY: true,
|
||||
transformArguments(key: RedisArgument, prefix: RedisArgument, options?: FtSugGetOptions) {
|
||||
const args = ['FT.SUGGET', key, prefix];
|
||||
|
||||
if (options?.FUZZY) {
|
||||
args.push('FUZZY');
|
||||
args.push('FUZZY');
|
||||
}
|
||||
|
||||
if (options?.MAX) {
|
||||
args.push('MAX', options.MAX.toString());
|
||||
if (options?.MAX !== undefined) {
|
||||
args.push('MAX', options.MAX.toString());
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
export declare function transformReply(): null | Array<string>;
|
||||
},
|
||||
transformReply: undefined as unknown as () => NullReply | ArrayReply<BlobStringReply>
|
||||
} as const satisfies Command;
|
||||
|
@@ -1,33 +1,35 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { transformArguments } from './SUGGET_WITHPAYLOADS';
|
||||
import SUGGET_WITHPAYLOADS from './SUGGET_WITHPAYLOADS';
|
||||
|
||||
describe('SUGGET WITHPAYLOADS', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('key', 'prefix'),
|
||||
['FT.SUGGET', 'key', 'prefix', 'WITHPAYLOADS']
|
||||
);
|
||||
});
|
||||
describe('FT.SUGGET WITHPAYLOADS', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
SUGGET_WITHPAYLOADS.transformArguments('key', 'prefix'),
|
||||
['FT.SUGGET', 'key', 'prefix', 'WITHPAYLOADS']
|
||||
);
|
||||
});
|
||||
|
||||
describe('client.ft.sugGetWithPayloads', () => {
|
||||
testUtils.testWithClient('null', async client => {
|
||||
assert.equal(
|
||||
await client.ft.sugGetWithPayloads('key', 'prefix'),
|
||||
null
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
describe('client.ft.sugGetWithPayloads', () => {
|
||||
testUtils.testWithClient('null', async client => {
|
||||
assert.equal(
|
||||
await client.ft.sugGetWithPayloads('key', 'prefix'),
|
||||
null
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
|
||||
testUtils.testWithClient('with suggestions', async client => {
|
||||
await client.ft.sugAdd('key', 'string', 1, { PAYLOAD: 'payload' });
|
||||
testUtils.testWithClient('with suggestions', async client => {
|
||||
const [, reply] = await Promise.all([
|
||||
client.ft.sugAdd('key', 'string', 1, {
|
||||
PAYLOAD: 'payload'
|
||||
}),
|
||||
client.ft.sugGetWithPayloads('key', 'string')
|
||||
]);
|
||||
|
||||
assert.deepEqual(
|
||||
await client.ft.sugGetWithPayloads('key', 'string'),
|
||||
[{
|
||||
suggestion: 'string',
|
||||
payload: 'payload'
|
||||
}]
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
||||
assert.deepEqual(reply, [{
|
||||
suggestion: 'string',
|
||||
payload: 'payload'
|
||||
}]);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
||||
});
|
||||
|
@@ -1,29 +1,31 @@
|
||||
import { SugGetOptions, transformArguments as transformSugGetArguments } from './SUGGET';
|
||||
import { NullReply, ArrayReply, BlobStringReply, UnwrapReply, Command } from '@redis/client/dist/lib/RESP/types';
|
||||
import { isNullReply } from '@redis/client/dist/lib/commands/generic-transformers';
|
||||
import SUGGET from './SUGGET';
|
||||
|
||||
export { IS_READ_ONLY } from './SUGGET';
|
||||
export default {
|
||||
FIRST_KEY_INDEX: SUGGET.FIRST_KEY_INDEX,
|
||||
IS_READ_ONLY: SUGGET.IS_READ_ONLY,
|
||||
transformArguments(...args: Parameters<typeof SUGGET.transformArguments>) {
|
||||
const transformedArguments = SUGGET.transformArguments(...args);
|
||||
transformedArguments.push('WITHPAYLOADS');
|
||||
return transformedArguments;
|
||||
},
|
||||
transformReply(reply: NullReply | UnwrapReply<ArrayReply<BlobStringReply>>) {
|
||||
if (isNullReply(reply)) return null;
|
||||
|
||||
export function transformArguments(key: string, prefix: string, options?: SugGetOptions): Array<string> {
|
||||
return [
|
||||
...transformSugGetArguments(key, prefix, options),
|
||||
'WITHPAYLOADS'
|
||||
];
|
||||
}
|
||||
|
||||
export interface SuggestionWithPayload {
|
||||
suggestion: string;
|
||||
payload: string | null;
|
||||
}
|
||||
|
||||
export function transformReply(rawReply: Array<string | null> | null): Array<SuggestionWithPayload> | null {
|
||||
if (rawReply === null) return null;
|
||||
|
||||
const transformedReply = [];
|
||||
for (let i = 0; i < rawReply.length; i += 2) {
|
||||
transformedReply.push({
|
||||
suggestion: rawReply[i]!,
|
||||
payload: rawReply[i + 1]
|
||||
});
|
||||
const transformedReply: Array<{
|
||||
suggestion: BlobStringReply;
|
||||
payload: BlobStringReply;
|
||||
}> = new Array(reply.length / 2);
|
||||
let replyIndex = 0,
|
||||
arrIndex = 0;
|
||||
while (replyIndex < reply.length) {
|
||||
transformedReply[arrIndex++] = {
|
||||
suggestion: reply[replyIndex++],
|
||||
payload: reply[replyIndex++]
|
||||
};
|
||||
}
|
||||
|
||||
return transformedReply;
|
||||
}
|
||||
}
|
||||
} as const satisfies Command;
|
||||
|
@@ -1,33 +1,33 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { transformArguments } from './SUGGET_WITHSCORES';
|
||||
import SUGGET_WITHSCORES from './SUGGET_WITHSCORES';
|
||||
|
||||
describe('SUGGET WITHSCORES', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('key', 'prefix'),
|
||||
['FT.SUGGET', 'key', 'prefix', 'WITHSCORES']
|
||||
);
|
||||
});
|
||||
describe('FT.SUGGET WITHSCORES', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
SUGGET_WITHSCORES.transformArguments('key', 'prefix'),
|
||||
['FT.SUGGET', 'key', 'prefix', 'WITHSCORES']
|
||||
);
|
||||
});
|
||||
|
||||
describe('client.ft.sugGetWithScores', () => {
|
||||
testUtils.testWithClient('null', async client => {
|
||||
assert.equal(
|
||||
await client.ft.sugGetWithScores('key', 'prefix'),
|
||||
null
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
describe('client.ft.sugGetWithScores', () => {
|
||||
testUtils.testWithClient('null', async client => {
|
||||
assert.equal(
|
||||
await client.ft.sugGetWithScores('key', 'prefix'),
|
||||
null
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
|
||||
testUtils.testWithClient('with suggestions', async client => {
|
||||
await client.ft.sugAdd('key', 'string', 1);
|
||||
testUtils.testWithClient('with suggestions', async client => {
|
||||
const [, reply] = await Promise.all([
|
||||
client.ft.sugAdd('key', 'string', 1),
|
||||
client.ft.sugGetWithScores('key', 's')
|
||||
]);
|
||||
|
||||
assert.deepEqual(
|
||||
await client.ft.sugGetWithScores('key', 'string'),
|
||||
[{
|
||||
suggestion: 'string',
|
||||
score: 2147483648
|
||||
}]
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
||||
assert.ok(Array.isArray(reply));
|
||||
assert.equal(reply.length, 1);
|
||||
assert.equal(reply[0].suggestion, 'string');
|
||||
assert.equal(typeof reply[0].score, 'number');
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
||||
});
|
||||
|
@@ -1,29 +1,50 @@
|
||||
import { SugGetOptions, transformArguments as transformSugGetArguments } from './SUGGET';
|
||||
import { NullReply, ArrayReply, BlobStringReply, DoubleReply, UnwrapReply, Command, TypeMapping } from '@redis/client/dist/lib/RESP/types';
|
||||
import { isNullReply, transformDoubleReply } from '@redis/client/dist/lib/commands/generic-transformers';
|
||||
import SUGGET from './SUGGET';
|
||||
|
||||
export { IS_READ_ONLY } from './SUGGET';
|
||||
|
||||
export function transformArguments(key: string, prefix: string, options?: SugGetOptions): Array<string> {
|
||||
return [
|
||||
...transformSugGetArguments(key, prefix, options),
|
||||
'WITHSCORES'
|
||||
];
|
||||
type SuggestScore = {
|
||||
suggestion: BlobStringReply;
|
||||
score: DoubleReply;
|
||||
}
|
||||
|
||||
export interface SuggestionWithScores {
|
||||
suggestion: string;
|
||||
score: number;
|
||||
}
|
||||
export default {
|
||||
FIRST_KEY_INDEX: SUGGET.FIRST_KEY_INDEX,
|
||||
IS_READ_ONLY: SUGGET.IS_READ_ONLY,
|
||||
transformArguments(...args: Parameters<typeof SUGGET.transformArguments>) {
|
||||
const transformedArguments = SUGGET.transformArguments(...args);
|
||||
transformedArguments.push('WITHSCORES');
|
||||
return transformedArguments;
|
||||
},
|
||||
transformReply: {
|
||||
2: (reply: NullReply | UnwrapReply<ArrayReply<BlobStringReply>>, preserve?: any, typeMapping?: TypeMapping) => {
|
||||
if (isNullReply(reply)) return null;
|
||||
|
||||
export function transformReply(rawReply: Array<string> | null): Array<SuggestionWithScores> | null {
|
||||
if (rawReply === null) return null;
|
||||
const transformedReply: Array<SuggestScore> = new Array(reply.length / 2);
|
||||
let replyIndex = 0,
|
||||
arrIndex = 0;
|
||||
while (replyIndex < reply.length) {
|
||||
transformedReply[arrIndex++] = {
|
||||
suggestion: reply[replyIndex++],
|
||||
score: transformDoubleReply[2](reply[replyIndex++], preserve, typeMapping)
|
||||
};
|
||||
}
|
||||
|
||||
const transformedReply = [];
|
||||
for (let i = 0; i < rawReply.length; i += 2) {
|
||||
transformedReply.push({
|
||||
suggestion: rawReply[i],
|
||||
score: Number(rawReply[i + 1])
|
||||
});
|
||||
return transformedReply;
|
||||
},
|
||||
3: (reply: UnwrapReply<ArrayReply<BlobStringReply | DoubleReply>>) => {
|
||||
if (isNullReply(reply)) return null;
|
||||
|
||||
const transformedReply: Array<SuggestScore> = new Array(reply.length / 2);
|
||||
let replyIndex = 0,
|
||||
arrIndex = 0;
|
||||
while (replyIndex < reply.length) {
|
||||
transformedReply[arrIndex++] = {
|
||||
suggestion: reply[replyIndex++] as BlobStringReply,
|
||||
score: reply[replyIndex++] as DoubleReply
|
||||
};
|
||||
}
|
||||
|
||||
return transformedReply;
|
||||
}
|
||||
|
||||
return transformedReply;
|
||||
}
|
||||
}
|
||||
} as const satisfies Command;
|
||||
|
@@ -1,34 +1,36 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { transformArguments } from './SUGGET_WITHSCORES_WITHPAYLOADS';
|
||||
import SUGGET_WITHSCORES_WITHPAYLOADS from './SUGGET_WITHSCORES_WITHPAYLOADS';
|
||||
|
||||
describe('SUGGET WITHSCORES WITHPAYLOADS', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('key', 'prefix'),
|
||||
['FT.SUGGET', 'key', 'prefix', 'WITHSCORES', 'WITHPAYLOADS']
|
||||
);
|
||||
});
|
||||
describe('FT.SUGGET WITHSCORES WITHPAYLOADS', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
SUGGET_WITHSCORES_WITHPAYLOADS.transformArguments('key', 'prefix'),
|
||||
['FT.SUGGET', 'key', 'prefix', 'WITHSCORES', 'WITHPAYLOADS']
|
||||
);
|
||||
});
|
||||
|
||||
describe('client.ft.sugGetWithScoresWithPayloads', () => {
|
||||
testUtils.testWithClient('null', async client => {
|
||||
assert.equal(
|
||||
await client.ft.sugGetWithScoresWithPayloads('key', 'prefix'),
|
||||
null
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
describe('client.ft.sugGetWithScoresWithPayloads', () => {
|
||||
testUtils.testWithClient('null', async client => {
|
||||
assert.equal(
|
||||
await client.ft.sugGetWithScoresWithPayloads('key', 'prefix'),
|
||||
null
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
|
||||
testUtils.testWithClient('with suggestions', async client => {
|
||||
await client.ft.sugAdd('key', 'string', 1, { PAYLOAD: 'payload' });
|
||||
testUtils.testWithClient('with suggestions', async client => {
|
||||
const [, reply] = await Promise.all([
|
||||
client.ft.sugAdd('key', 'string', 1, {
|
||||
PAYLOAD: 'payload'
|
||||
}),
|
||||
client.ft.sugGetWithScoresWithPayloads('key', 'string')
|
||||
]);
|
||||
|
||||
assert.deepEqual(
|
||||
await client.ft.sugGetWithScoresWithPayloads('key', 'string'),
|
||||
[{
|
||||
suggestion: 'string',
|
||||
score: 2147483648,
|
||||
payload: 'payload'
|
||||
}]
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
||||
assert.ok(Array.isArray(reply));
|
||||
assert.equal(reply.length, 1);
|
||||
assert.equal(reply[0].suggestion, 'string');
|
||||
assert.equal(typeof reply[0].score, 'number');
|
||||
assert.equal(reply[0].payload, 'payload');
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
||||
});
|
||||
|
@@ -1,30 +1,56 @@
|
||||
import { SugGetOptions, transformArguments as transformSugGetArguments } from './SUGGET';
|
||||
import { SuggestionWithPayload } from './SUGGET_WITHPAYLOADS';
|
||||
import { SuggestionWithScores } from './SUGGET_WITHSCORES';
|
||||
import { NullReply, ArrayReply, BlobStringReply, DoubleReply, UnwrapReply, Command, TypeMapping } from '@redis/client/dist/lib/RESP/types';
|
||||
import { isNullReply, transformDoubleReply } from '@redis/client/dist/lib/commands/generic-transformers';
|
||||
import SUGGET from './SUGGET';
|
||||
|
||||
export { IS_READ_ONLY } from './SUGGET';
|
||||
|
||||
export function transformArguments(key: string, prefix: string, options?: SugGetOptions): Array<string> {
|
||||
return [
|
||||
...transformSugGetArguments(key, prefix, options),
|
||||
'WITHSCORES',
|
||||
'WITHPAYLOADS'
|
||||
];
|
||||
type SuggestScoreWithPayload = {
|
||||
suggestion: BlobStringReply;
|
||||
score: DoubleReply;
|
||||
payload: BlobStringReply;
|
||||
}
|
||||
|
||||
type SuggestionWithScoresAndPayloads = SuggestionWithScores & SuggestionWithPayload;
|
||||
export default {
|
||||
FIRST_KEY_INDEX: SUGGET.FIRST_KEY_INDEX,
|
||||
IS_READ_ONLY: SUGGET.IS_READ_ONLY,
|
||||
transformArguments(...args: Parameters<typeof SUGGET.transformArguments>) {
|
||||
const transformedArguments = SUGGET.transformArguments(...args);
|
||||
transformedArguments.push(
|
||||
'WITHSCORES',
|
||||
'WITHPAYLOADS'
|
||||
);
|
||||
return transformedArguments;
|
||||
},
|
||||
transformReply: {
|
||||
2: (reply: NullReply | UnwrapReply<ArrayReply<BlobStringReply>>, preserve?: any, typeMapping?: TypeMapping) => {
|
||||
if (isNullReply(reply)) return null;
|
||||
|
||||
export function transformReply(rawReply: Array<string | null> | null): Array<SuggestionWithScoresAndPayloads> | null {
|
||||
if (rawReply === null) return null;
|
||||
const transformedReply: Array<SuggestScoreWithPayload> = new Array(reply.length / 3);
|
||||
let replyIndex = 0,
|
||||
arrIndex = 0;
|
||||
while (replyIndex < reply.length) {
|
||||
transformedReply[arrIndex++] = {
|
||||
suggestion: reply[replyIndex++],
|
||||
score: transformDoubleReply[2](reply[replyIndex++], preserve, typeMapping),
|
||||
payload: reply[replyIndex++]
|
||||
};
|
||||
}
|
||||
|
||||
const transformedReply = [];
|
||||
for (let i = 0; i < rawReply.length; i += 3) {
|
||||
transformedReply.push({
|
||||
suggestion: rawReply[i]!,
|
||||
score: Number(rawReply[i + 1]!),
|
||||
payload: rawReply[i + 2]
|
||||
});
|
||||
return transformedReply;
|
||||
},
|
||||
3: (reply: NullReply | UnwrapReply<ArrayReply<BlobStringReply | DoubleReply>>) => {
|
||||
if (isNullReply(reply)) return null;
|
||||
|
||||
const transformedReply: Array<SuggestScoreWithPayload> = new Array(reply.length / 3);
|
||||
let replyIndex = 0,
|
||||
arrIndex = 0;
|
||||
while (replyIndex < reply.length) {
|
||||
transformedReply[arrIndex++] = {
|
||||
suggestion: reply[replyIndex++] as BlobStringReply,
|
||||
score: reply[replyIndex++] as DoubleReply,
|
||||
payload: reply[replyIndex++] as BlobStringReply
|
||||
};
|
||||
}
|
||||
|
||||
return transformedReply;
|
||||
}
|
||||
|
||||
return transformedReply;
|
||||
}
|
||||
}
|
||||
} as const satisfies Command;
|
||||
|
@@ -1,19 +1,19 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { transformArguments } from './SUGLEN';
|
||||
import SUGLEN from './SUGLEN';
|
||||
|
||||
describe('SUGLEN', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('key'),
|
||||
['FT.SUGLEN', 'key']
|
||||
);
|
||||
});
|
||||
describe('FT.SUGLEN', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
SUGLEN.transformArguments('key'),
|
||||
['FT.SUGLEN', 'key']
|
||||
);
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.sugLen', async client => {
|
||||
assert.equal(
|
||||
await client.ft.sugLen('key'),
|
||||
0
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
testUtils.testWithClient('client.ft.sugLen', async client => {
|
||||
assert.equal(
|
||||
await client.ft.sugLen('key'),
|
||||
0
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
||||
|
@@ -1,7 +1,10 @@
|
||||
export const IS_READ_ONLY = true;
|
||||
import { RedisArgument, NumberReply, Command } from '@redis/client/dist/lib/RESP/types';
|
||||
|
||||
export function transformArguments(key: string): Array<string> {
|
||||
export default {
|
||||
FIRST_KEY_INDEX: 1,
|
||||
IS_READ_ONLY: true,
|
||||
transformArguments(key: RedisArgument) {
|
||||
return ['FT.SUGLEN', key];
|
||||
}
|
||||
|
||||
export declare function transformReply(): number;
|
||||
},
|
||||
transformReply: undefined as unknown as () => NumberReply
|
||||
} as const satisfies Command;
|
||||
|
@@ -1,24 +1,24 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { transformArguments } from './SYNDUMP';
|
||||
import { SchemaFieldTypes } from '.';
|
||||
import SYNDUMP from './SYNDUMP';
|
||||
import { SCHEMA_FIELD_TYPE } from './CREATE';
|
||||
|
||||
describe('SYNDUMP', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index'),
|
||||
['FT.SYNDUMP', 'index']
|
||||
);
|
||||
});
|
||||
describe('FT.SYNDUMP', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
SYNDUMP.transformArguments('index'),
|
||||
['FT.SYNDUMP', 'index']
|
||||
);
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.synDump', async client => {
|
||||
await client.ft.create('index', {
|
||||
field: SchemaFieldTypes.TEXT
|
||||
});
|
||||
testUtils.testWithClient('client.ft.synDump', async client => {
|
||||
const [, reply] = await Promise.all([
|
||||
client.ft.create('index', {
|
||||
field: SCHEMA_FIELD_TYPE.TEXT
|
||||
}),
|
||||
client.ft.synDump('index')
|
||||
]);
|
||||
|
||||
assert.deepEqual(
|
||||
await client.ft.synDump('index'),
|
||||
[]
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
assert.deepEqual(reply, {});
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
||||
|
@@ -1,5 +1,22 @@
|
||||
export function transformArguments(index: string): Array<string> {
|
||||
return ['FT.SYNDUMP', index];
|
||||
}
|
||||
import { RedisArgument, MapReply, BlobStringReply, ArrayReply, UnwrapReply, Command } from '@redis/client/dist/lib/RESP/types';
|
||||
|
||||
export declare function transformReply(): Array<string>;
|
||||
export default {
|
||||
FIRST_KEY_INDEX: undefined,
|
||||
IS_READ_ONLY: true,
|
||||
transformArguments(index: RedisArgument) {
|
||||
return ['FT.SYNDUMP', index];
|
||||
},
|
||||
transformReply: {
|
||||
2: (reply: UnwrapReply<ArrayReply<BlobStringReply | ArrayReply<BlobStringReply>>>) => {
|
||||
const result: Record<string, ArrayReply<BlobStringReply>> = {};
|
||||
let i = 0;
|
||||
while (i < reply.length) {
|
||||
const key = (reply[i++] as unknown as UnwrapReply<BlobStringReply>).toString(),
|
||||
value = reply[i++] as unknown as ArrayReply<BlobStringReply>;
|
||||
result[key] = value;
|
||||
}
|
||||
return result;
|
||||
},
|
||||
3: undefined as unknown as () => MapReply<BlobStringReply, ArrayReply<BlobStringReply>>
|
||||
}
|
||||
} as const satisfies Command;
|
||||
|
@@ -1,40 +1,42 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { transformArguments } from './SYNUPDATE';
|
||||
import { SchemaFieldTypes } from '.';
|
||||
import SYNUPDATE from './SYNUPDATE';
|
||||
import { SCHEMA_FIELD_TYPE } from './CREATE';
|
||||
|
||||
describe('SYNUPDATE', () => {
|
||||
describe('transformArguments', () => {
|
||||
it('single term', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'groupId', 'term'),
|
||||
['FT.SYNUPDATE', 'index', 'groupId', 'term']
|
||||
);
|
||||
});
|
||||
|
||||
it('multiple terms', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'groupId', ['1', '2']),
|
||||
['FT.SYNUPDATE', 'index', 'groupId', '1', '2']
|
||||
);
|
||||
});
|
||||
|
||||
it('with SKIPINITIALSCAN', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'groupId', 'term', { SKIPINITIALSCAN: true }),
|
||||
['FT.SYNUPDATE', 'index', 'groupId', 'SKIPINITIALSCAN', 'term']
|
||||
);
|
||||
});
|
||||
describe('FT.SYNUPDATE', () => {
|
||||
describe('transformArguments', () => {
|
||||
it('single term', () => {
|
||||
assert.deepEqual(
|
||||
SYNUPDATE.transformArguments('index', 'groupId', 'term'),
|
||||
['FT.SYNUPDATE', 'index', 'groupId', 'term']
|
||||
);
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.synUpdate', async client => {
|
||||
await client.ft.create('index', {
|
||||
field: SchemaFieldTypes.TEXT
|
||||
});
|
||||
it('multiple terms', () => {
|
||||
assert.deepEqual(
|
||||
SYNUPDATE.transformArguments('index', 'groupId', ['1', '2']),
|
||||
['FT.SYNUPDATE', 'index', 'groupId', '1', '2']
|
||||
);
|
||||
});
|
||||
|
||||
assert.equal(
|
||||
await client.ft.synUpdate('index', 'groupId', 'term'),
|
||||
'OK'
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
it('with SKIPINITIALSCAN', () => {
|
||||
assert.deepEqual(
|
||||
SYNUPDATE.transformArguments('index', 'groupId', 'term', {
|
||||
SKIPINITIALSCAN: true
|
||||
}),
|
||||
['FT.SYNUPDATE', 'index', 'groupId', 'SKIPINITIALSCAN', 'term']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.synUpdate', async client => {
|
||||
const [, reply] = await Promise.all([
|
||||
client.ft.create('index', {
|
||||
field: SCHEMA_FIELD_TYPE.TEXT
|
||||
}),
|
||||
client.ft.synUpdate('index', 'groupId', 'term')
|
||||
]);
|
||||
|
||||
assert.equal(reply, 'OK');
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
||||
|
@@ -1,23 +1,26 @@
|
||||
import { pushVerdictArguments } from '@redis/client/dist/lib/commands/generic-transformers';
|
||||
import { RedisCommandArguments } from '@redis/client/dist/lib/commands';
|
||||
import { SimpleStringReply, Command, RedisArgument } from '@redis/client/dist/lib/RESP/types';
|
||||
import { RedisVariadicArgument, pushVariadicArguments } from '@redis/client/dist/lib/commands/generic-transformers';
|
||||
|
||||
interface SynUpdateOptions {
|
||||
SKIPINITIALSCAN?: true;
|
||||
export interface FtSynUpdateOptions {
|
||||
SKIPINITIALSCAN?: boolean;
|
||||
}
|
||||
|
||||
export function transformArguments(
|
||||
index: string,
|
||||
groupId: string,
|
||||
terms: string | Array<string>,
|
||||
options?: SynUpdateOptions
|
||||
): RedisCommandArguments {
|
||||
export default {
|
||||
FIRST_KEY_INDEX: undefined,
|
||||
IS_READ_ONLY: true,
|
||||
transformArguments(
|
||||
index: RedisArgument,
|
||||
groupId: RedisArgument,
|
||||
terms: RedisVariadicArgument,
|
||||
options?: FtSynUpdateOptions
|
||||
) {
|
||||
const args = ['FT.SYNUPDATE', index, groupId];
|
||||
|
||||
if (options?.SKIPINITIALSCAN) {
|
||||
args.push('SKIPINITIALSCAN');
|
||||
args.push('SKIPINITIALSCAN');
|
||||
}
|
||||
|
||||
return pushVerdictArguments(args, terms);
|
||||
}
|
||||
|
||||
export declare function transformReply(): 'OK';
|
||||
return pushVariadicArguments(args, terms);
|
||||
},
|
||||
transformReply: undefined as unknown as () => SimpleStringReply<'OK'>
|
||||
} as const satisfies Command;
|
||||
|
@@ -1,24 +1,24 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { SchemaFieldTypes } from '.';
|
||||
import { transformArguments } from './TAGVALS';
|
||||
import TAGVALS from './TAGVALS';
|
||||
import { SCHEMA_FIELD_TYPE } from './CREATE';
|
||||
|
||||
describe('TAGVALS', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', '@field'),
|
||||
['FT.TAGVALS', 'index', '@field']
|
||||
);
|
||||
});
|
||||
describe('FT.TAGVALS', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
TAGVALS.transformArguments('index', '@field'),
|
||||
['FT.TAGVALS', 'index', '@field']
|
||||
);
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.tagVals', async client => {
|
||||
await client.ft.create('index', {
|
||||
field: SchemaFieldTypes.TAG
|
||||
});
|
||||
testUtils.testWithClient('client.ft.tagVals', async client => {
|
||||
const [, reply] = await Promise.all([
|
||||
client.ft.create('index', {
|
||||
field: SCHEMA_FIELD_TYPE.TAG
|
||||
}),
|
||||
client.ft.tagVals('index', 'field')
|
||||
]);
|
||||
|
||||
assert.deepEqual(
|
||||
await client.ft.tagVals('index', 'field'),
|
||||
[]
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
assert.deepEqual(reply, []);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
||||
|
@@ -1,5 +1,13 @@
|
||||
export function transformArguments(index: string, fieldName: string): Array<string> {
|
||||
return ['FT.TAGVALS', index, fieldName];
|
||||
}
|
||||
import { RedisArgument, ArrayReply, SetReply, BlobStringReply, Command } from '@redis/client/dist/lib/RESP/types';
|
||||
|
||||
export declare function transformReply(): Array<string>;
|
||||
export default {
|
||||
FIRST_KEY_INDEX: undefined,
|
||||
IS_READ_ONLY: true,
|
||||
transformArguments(index: RedisArgument, fieldName: RedisArgument) {
|
||||
return ['FT.TAGVALS', index, fieldName];
|
||||
},
|
||||
transformReply: {
|
||||
2: undefined as unknown as () => ArrayReply<BlobStringReply>,
|
||||
3: undefined as unknown as () => SetReply<BlobStringReply>
|
||||
}
|
||||
} as const satisfies Command;
|
||||
|
@@ -1,19 +1,19 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { transformArguments } from './_LIST';
|
||||
import _LIST from './_LIST';
|
||||
|
||||
describe('_LIST', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments(),
|
||||
['FT._LIST']
|
||||
);
|
||||
});
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
_LIST.transformArguments(),
|
||||
['FT._LIST']
|
||||
);
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft._list', async client => {
|
||||
assert.deepEqual(
|
||||
await client.ft._list(),
|
||||
[]
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
testUtils.testWithClient('client.ft._list', async client => {
|
||||
assert.deepEqual(
|
||||
await client.ft._list(),
|
||||
[]
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
||||
|
@@ -1,5 +1,13 @@
|
||||
export function transformArguments(): Array<string> {
|
||||
return ['FT._LIST'];
|
||||
}
|
||||
import { ArrayReply, SetReply, BlobStringReply, Command } from '@redis/client/dist/lib/RESP/types';
|
||||
|
||||
export declare function transformReply(): Array<string>;
|
||||
export default {
|
||||
FIRST_KEY_INDEX: undefined,
|
||||
IS_READ_ONLY: true,
|
||||
transformArguments() {
|
||||
return ['FT._LIST'];
|
||||
},
|
||||
transformReply: {
|
||||
2: undefined as unknown as () => ArrayReply<BlobStringReply>,
|
||||
3: undefined as unknown as () => SetReply<BlobStringReply>
|
||||
}
|
||||
} as const satisfies Command;
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import { pushArgumentsWithLength, pushSortByArguments } from '.';
|
||||
import { strict as assert } from 'node:assert';
|
||||
|
||||
/* import { pushArgumentsWithLength, pushSortByArguments } from '.';
|
||||
|
||||
describe('pushSortByArguments', () => {
|
||||
describe('single', () => {
|
||||
@@ -44,3 +45,4 @@ it('pushArgumentsWithLength', () => {
|
||||
['a', '2', 'b', 'c']
|
||||
);
|
||||
});
|
||||
*/
|
@@ -1,690 +1,105 @@
|
||||
import * as _LIST from './_LIST';
|
||||
import * as ALTER from './ALTER';
|
||||
import * as AGGREGATE_WITHCURSOR from './AGGREGATE_WITHCURSOR';
|
||||
import * as AGGREGATE from './AGGREGATE';
|
||||
import * as ALIASADD from './ALIASADD';
|
||||
import * as ALIASDEL from './ALIASDEL';
|
||||
import * as ALIASUPDATE from './ALIASUPDATE';
|
||||
import * as CONFIG_GET from './CONFIG_GET';
|
||||
import * as CONFIG_SET from './CONFIG_SET';
|
||||
import * as CREATE from './CREATE';
|
||||
import * as CURSOR_DEL from './CURSOR_DEL';
|
||||
import * as CURSOR_READ from './CURSOR_READ';
|
||||
import * as DICTADD from './DICTADD';
|
||||
import * as DICTDEL from './DICTDEL';
|
||||
import * as DICTDUMP from './DICTDUMP';
|
||||
import * as DROPINDEX from './DROPINDEX';
|
||||
import * as EXPLAIN from './EXPLAIN';
|
||||
import * as EXPLAINCLI from './EXPLAINCLI';
|
||||
import * as INFO from './INFO';
|
||||
import * as PROFILESEARCH from './PROFILE_SEARCH';
|
||||
import * as PROFILEAGGREGATE from './PROFILE_AGGREGATE';
|
||||
import * as SEARCH from './SEARCH';
|
||||
import * as SEARCH_NOCONTENT from './SEARCH_NOCONTENT';
|
||||
import * as SPELLCHECK from './SPELLCHECK';
|
||||
import * as SUGADD from './SUGADD';
|
||||
import * as SUGDEL from './SUGDEL';
|
||||
import * as SUGGET_WITHPAYLOADS from './SUGGET_WITHPAYLOADS';
|
||||
import * as SUGGET_WITHSCORES_WITHPAYLOADS from './SUGGET_WITHSCORES_WITHPAYLOADS';
|
||||
import * as SUGGET_WITHSCORES from './SUGGET_WITHSCORES';
|
||||
import * as SUGGET from './SUGGET';
|
||||
import * as SUGLEN from './SUGLEN';
|
||||
import * as SYNDUMP from './SYNDUMP';
|
||||
import * as SYNUPDATE from './SYNUPDATE';
|
||||
import * as TAGVALS from './TAGVALS';
|
||||
import { RedisCommandArgument, RedisCommandArguments } from '@redis/client/dist/lib/commands';
|
||||
import { pushOptionalVerdictArgument, pushVerdictArgument } from '@redis/client/dist/lib/commands/generic-transformers';
|
||||
import { SearchOptions } from './SEARCH';
|
||||
import _LIST from './_LIST';
|
||||
import ALTER from './ALTER';
|
||||
import AGGREGATE_WITHCURSOR from './AGGREGATE_WITHCURSOR';
|
||||
import AGGREGATE from './AGGREGATE';
|
||||
import ALIASADD from './ALIASADD';
|
||||
import ALIASDEL from './ALIASDEL';
|
||||
import ALIASUPDATE from './ALIASUPDATE';
|
||||
import CONFIG_GET from './CONFIG_GET';
|
||||
import CONFIG_SET from './CONFIG_SET';
|
||||
import CREATE from './CREATE';
|
||||
import CURSOR_DEL from './CURSOR_DEL';
|
||||
import CURSOR_READ from './CURSOR_READ';
|
||||
import DICTADD from './DICTADD';
|
||||
import DICTDEL from './DICTDEL';
|
||||
import DICTDUMP from './DICTDUMP';
|
||||
import DROPINDEX from './DROPINDEX';
|
||||
import EXPLAIN from './EXPLAIN';
|
||||
import EXPLAINCLI from './EXPLAINCLI';
|
||||
import INFO from './INFO';
|
||||
import PROFILESEARCH from './PROFILE_SEARCH';
|
||||
import PROFILEAGGREGATE from './PROFILE_AGGREGATE';
|
||||
import SEARCH_NOCONTENT from './SEARCH_NOCONTENT';
|
||||
import SEARCH from './SEARCH';
|
||||
import SPELLCHECK from './SPELLCHECK';
|
||||
import SUGADD from './SUGADD';
|
||||
import SUGDEL from './SUGDEL';
|
||||
import SUGGET_WITHPAYLOADS from './SUGGET_WITHPAYLOADS';
|
||||
import SUGGET_WITHSCORES_WITHPAYLOADS from './SUGGET_WITHSCORES_WITHPAYLOADS';
|
||||
import SUGGET_WITHSCORES from './SUGGET_WITHSCORES';
|
||||
import SUGGET from './SUGGET';
|
||||
import SUGLEN from './SUGLEN';
|
||||
import SYNDUMP from './SYNDUMP';
|
||||
import SYNUPDATE from './SYNUPDATE';
|
||||
import TAGVALS from './TAGVALS';
|
||||
|
||||
export default {
|
||||
_LIST,
|
||||
_list: _LIST,
|
||||
ALTER,
|
||||
alter: ALTER,
|
||||
AGGREGATE_WITHCURSOR,
|
||||
aggregateWithCursor: AGGREGATE_WITHCURSOR,
|
||||
AGGREGATE,
|
||||
aggregate: AGGREGATE,
|
||||
ALIASADD,
|
||||
aliasAdd: ALIASADD,
|
||||
ALIASDEL,
|
||||
aliasDel: ALIASDEL,
|
||||
ALIASUPDATE,
|
||||
aliasUpdate: ALIASUPDATE,
|
||||
CONFIG_GET,
|
||||
configGet: CONFIG_GET,
|
||||
CONFIG_SET,
|
||||
configSet: CONFIG_SET,
|
||||
CREATE,
|
||||
create: CREATE,
|
||||
CURSOR_DEL,
|
||||
cursorDel: CURSOR_DEL,
|
||||
CURSOR_READ,
|
||||
cursorRead: CURSOR_READ,
|
||||
DICTADD,
|
||||
dictAdd: DICTADD,
|
||||
DICTDEL,
|
||||
dictDel: DICTDEL,
|
||||
DICTDUMP,
|
||||
dictDump: DICTDUMP,
|
||||
DROPINDEX,
|
||||
dropIndex: DROPINDEX,
|
||||
EXPLAIN,
|
||||
explain: EXPLAIN,
|
||||
EXPLAINCLI,
|
||||
explainCli: EXPLAINCLI,
|
||||
INFO,
|
||||
info: INFO,
|
||||
PROFILESEARCH,
|
||||
profileSearch: PROFILESEARCH,
|
||||
PROFILEAGGREGATE,
|
||||
profileAggregate: PROFILEAGGREGATE,
|
||||
SEARCH,
|
||||
search: SEARCH,
|
||||
SEARCH_NOCONTENT,
|
||||
searchNoContent: SEARCH_NOCONTENT,
|
||||
SPELLCHECK,
|
||||
spellCheck: SPELLCHECK,
|
||||
SUGADD,
|
||||
sugAdd: SUGADD,
|
||||
SUGDEL,
|
||||
sugDel: SUGDEL,
|
||||
SUGGET_WITHPAYLOADS,
|
||||
sugGetWithPayloads: SUGGET_WITHPAYLOADS,
|
||||
SUGGET_WITHSCORES_WITHPAYLOADS,
|
||||
sugGetWithScoresWithPayloads: SUGGET_WITHSCORES_WITHPAYLOADS,
|
||||
SUGGET_WITHSCORES,
|
||||
sugGetWithScores: SUGGET_WITHSCORES,
|
||||
SUGGET,
|
||||
sugGet: SUGGET,
|
||||
SUGLEN,
|
||||
sugLen: SUGLEN,
|
||||
SYNDUMP,
|
||||
synDump: SYNDUMP,
|
||||
SYNUPDATE,
|
||||
synUpdate: SYNUPDATE,
|
||||
TAGVALS,
|
||||
tagVals: TAGVALS
|
||||
_LIST,
|
||||
_list: _LIST,
|
||||
ALTER,
|
||||
alter: ALTER,
|
||||
AGGREGATE_WITHCURSOR,
|
||||
aggregateWithCursor: AGGREGATE_WITHCURSOR,
|
||||
AGGREGATE,
|
||||
aggregate: AGGREGATE,
|
||||
ALIASADD,
|
||||
aliasAdd: ALIASADD,
|
||||
ALIASDEL,
|
||||
aliasDel: ALIASDEL,
|
||||
ALIASUPDATE,
|
||||
aliasUpdate: ALIASUPDATE,
|
||||
CONFIG_GET,
|
||||
configGet: CONFIG_GET,
|
||||
CONFIG_SET,
|
||||
configSet: CONFIG_SET,
|
||||
CREATE,
|
||||
create: CREATE,
|
||||
CURSOR_DEL,
|
||||
cursorDel: CURSOR_DEL,
|
||||
CURSOR_READ,
|
||||
cursorRead: CURSOR_READ,
|
||||
DICTADD,
|
||||
dictAdd: DICTADD,
|
||||
DICTDEL,
|
||||
dictDel: DICTDEL,
|
||||
DICTDUMP,
|
||||
dictDump: DICTDUMP,
|
||||
DROPINDEX,
|
||||
dropIndex: DROPINDEX,
|
||||
EXPLAIN,
|
||||
explain: EXPLAIN,
|
||||
EXPLAINCLI,
|
||||
explainCli: EXPLAINCLI,
|
||||
INFO,
|
||||
info: INFO,
|
||||
PROFILESEARCH,
|
||||
profileSearch: PROFILESEARCH,
|
||||
PROFILEAGGREGATE,
|
||||
profileAggregate: PROFILEAGGREGATE,
|
||||
SEARCH_NOCONTENT,
|
||||
searchNoContent: SEARCH_NOCONTENT,
|
||||
SEARCH,
|
||||
search: SEARCH,
|
||||
SPELLCHECK,
|
||||
spellCheck: SPELLCHECK,
|
||||
SUGADD,
|
||||
sugAdd: SUGADD,
|
||||
SUGDEL,
|
||||
sugDel: SUGDEL,
|
||||
SUGGET_WITHPAYLOADS,
|
||||
sugGetWithPayloads: SUGGET_WITHPAYLOADS,
|
||||
SUGGET_WITHSCORES_WITHPAYLOADS,
|
||||
sugGetWithScoresWithPayloads: SUGGET_WITHSCORES_WITHPAYLOADS,
|
||||
SUGGET_WITHSCORES,
|
||||
sugGetWithScores: SUGGET_WITHSCORES,
|
||||
SUGGET,
|
||||
sugGet: SUGGET,
|
||||
SUGLEN,
|
||||
sugLen: SUGLEN,
|
||||
SYNDUMP,
|
||||
synDump: SYNDUMP,
|
||||
SYNUPDATE,
|
||||
synUpdate: SYNUPDATE,
|
||||
TAGVALS,
|
||||
tagVals: TAGVALS
|
||||
};
|
||||
|
||||
export enum RedisSearchLanguages {
|
||||
ARABIC = 'Arabic',
|
||||
BASQUE = 'Basque',
|
||||
CATALANA = 'Catalan',
|
||||
DANISH = 'Danish',
|
||||
DUTCH = 'Dutch',
|
||||
ENGLISH = 'English',
|
||||
FINNISH = 'Finnish',
|
||||
FRENCH = 'French',
|
||||
GERMAN = 'German',
|
||||
GREEK = 'Greek',
|
||||
HUNGARIAN = 'Hungarian',
|
||||
INDONESAIN = 'Indonesian',
|
||||
IRISH = 'Irish',
|
||||
ITALIAN = 'Italian',
|
||||
LITHUANIAN = 'Lithuanian',
|
||||
NEPALI = 'Nepali',
|
||||
NORWEIGAN = 'Norwegian',
|
||||
PORTUGUESE = 'Portuguese',
|
||||
ROMANIAN = 'Romanian',
|
||||
RUSSIAN = 'Russian',
|
||||
SPANISH = 'Spanish',
|
||||
SWEDISH = 'Swedish',
|
||||
TAMIL = 'Tamil',
|
||||
TURKISH = 'Turkish',
|
||||
CHINESE = 'Chinese'
|
||||
}
|
||||
|
||||
export type PropertyName = `${'@' | '$.'}${string}`;
|
||||
|
||||
export type SortByProperty = string | {
|
||||
BY: string;
|
||||
DIRECTION?: 'ASC' | 'DESC';
|
||||
};
|
||||
|
||||
export function pushSortByProperty(args: RedisCommandArguments, sortBy: SortByProperty): void {
|
||||
if (typeof sortBy === 'string') {
|
||||
args.push(sortBy);
|
||||
} else {
|
||||
args.push(sortBy.BY);
|
||||
|
||||
if (sortBy.DIRECTION) {
|
||||
args.push(sortBy.DIRECTION);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function pushSortByArguments(args: RedisCommandArguments, name: string, sortBy: SortByProperty | Array<SortByProperty>): RedisCommandArguments {
|
||||
const lengthBefore = args.push(
|
||||
name,
|
||||
'' // will be overwritten
|
||||
);
|
||||
|
||||
if (Array.isArray(sortBy)) {
|
||||
for (const field of sortBy) {
|
||||
pushSortByProperty(args, field);
|
||||
}
|
||||
} else {
|
||||
pushSortByProperty(args, sortBy);
|
||||
}
|
||||
|
||||
args[lengthBefore - 1] = (args.length - lengthBefore).toString();
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
export function pushArgumentsWithLength(args: RedisCommandArguments, fn: (args: RedisCommandArguments) => void): RedisCommandArguments {
|
||||
const lengthIndex = args.push('') - 1;
|
||||
fn(args);
|
||||
args[lengthIndex] = (args.length - lengthIndex - 1).toString();
|
||||
return args;
|
||||
}
|
||||
|
||||
export enum SchemaFieldTypes {
|
||||
TEXT = 'TEXT',
|
||||
NUMERIC = 'NUMERIC',
|
||||
GEO = 'GEO',
|
||||
TAG = 'TAG',
|
||||
VECTOR = 'VECTOR',
|
||||
GEOSHAPE = 'GEOSHAPE'
|
||||
}
|
||||
|
||||
type CreateSchemaField<
|
||||
T extends SchemaFieldTypes,
|
||||
E = Record<PropertyKey, unknown>
|
||||
> = T | ({
|
||||
type: T;
|
||||
AS?: string;
|
||||
INDEXMISSING?: boolean;
|
||||
} & E);
|
||||
|
||||
type CommonFieldArguments = {
|
||||
SORTABLE?: boolean | 'UNF';
|
||||
NOINDEX?: boolean;
|
||||
};
|
||||
|
||||
type CreateSchemaCommonField<
|
||||
T extends SchemaFieldTypes,
|
||||
E = Record<PropertyKey, unknown>
|
||||
> = CreateSchemaField<
|
||||
T,
|
||||
(CommonFieldArguments & E)
|
||||
>;
|
||||
|
||||
function pushCommonFieldArguments(args: RedisCommandArguments, fieldOptions: CommonFieldArguments) {
|
||||
if (fieldOptions.SORTABLE) {
|
||||
args.push('SORTABLE');
|
||||
|
||||
if (fieldOptions.SORTABLE === 'UNF') {
|
||||
args.push('UNF');
|
||||
}
|
||||
}
|
||||
|
||||
if (fieldOptions.NOINDEX) {
|
||||
args.push('NOINDEX');
|
||||
}
|
||||
}
|
||||
|
||||
export enum SchemaTextFieldPhonetics {
|
||||
DM_EN = 'dm:en',
|
||||
DM_FR = 'dm:fr',
|
||||
FM_PT = 'dm:pt',
|
||||
DM_ES = 'dm:es'
|
||||
}
|
||||
|
||||
type CreateSchemaTextField = CreateSchemaCommonField<SchemaFieldTypes.TEXT, {
|
||||
NOSTEM?: true;
|
||||
WEIGHT?: number;
|
||||
PHONETIC?: SchemaTextFieldPhonetics;
|
||||
WITHSUFFIXTRIE?: boolean;
|
||||
INDEXEMPTY?: boolean;
|
||||
}>;
|
||||
|
||||
type CreateSchemaNumericField = CreateSchemaCommonField<SchemaFieldTypes.NUMERIC>;
|
||||
|
||||
type CreateSchemaGeoField = CreateSchemaCommonField<SchemaFieldTypes.GEO>;
|
||||
|
||||
type CreateSchemaTagField = CreateSchemaCommonField<SchemaFieldTypes.TAG, {
|
||||
SEPARATOR?: string;
|
||||
CASESENSITIVE?: true;
|
||||
WITHSUFFIXTRIE?: boolean;
|
||||
INDEXEMPTY?: boolean;
|
||||
}>;
|
||||
|
||||
export enum VectorAlgorithms {
|
||||
FLAT = 'FLAT',
|
||||
HNSW = 'HNSW'
|
||||
}
|
||||
|
||||
type CreateSchemaVectorField<
|
||||
T extends VectorAlgorithms,
|
||||
A extends Record<string, unknown>
|
||||
> = CreateSchemaField<SchemaFieldTypes.VECTOR, {
|
||||
ALGORITHM: T;
|
||||
TYPE: string;
|
||||
DIM: number;
|
||||
DISTANCE_METRIC: 'L2' | 'IP' | 'COSINE';
|
||||
INITIAL_CAP?: number;
|
||||
} & A>;
|
||||
|
||||
type CreateSchemaFlatVectorField = CreateSchemaVectorField<VectorAlgorithms.FLAT, {
|
||||
BLOCK_SIZE?: number;
|
||||
}>;
|
||||
|
||||
type CreateSchemaHNSWVectorField = CreateSchemaVectorField<VectorAlgorithms.HNSW, {
|
||||
M?: number;
|
||||
EF_CONSTRUCTION?: number;
|
||||
EF_RUNTIME?: number;
|
||||
}>;
|
||||
|
||||
export const SCHEMA_GEO_SHAPE_COORD_SYSTEM = {
|
||||
SPHERICAL: 'SPHERICAL',
|
||||
FLAT: 'FLAT'
|
||||
} as const;
|
||||
|
||||
export type SchemaGeoShapeFieldCoordSystem = typeof SCHEMA_GEO_SHAPE_COORD_SYSTEM[keyof typeof SCHEMA_GEO_SHAPE_COORD_SYSTEM];
|
||||
|
||||
type CreateSchemaGeoShapeField = CreateSchemaCommonField<SchemaFieldTypes.GEOSHAPE, {
|
||||
COORD_SYSTEM?: SchemaGeoShapeFieldCoordSystem;
|
||||
}>;
|
||||
|
||||
export interface RediSearchSchema {
|
||||
[field: string]:
|
||||
CreateSchemaTextField |
|
||||
CreateSchemaNumericField |
|
||||
CreateSchemaGeoField |
|
||||
CreateSchemaTagField |
|
||||
CreateSchemaFlatVectorField |
|
||||
CreateSchemaHNSWVectorField |
|
||||
CreateSchemaGeoShapeField;
|
||||
}
|
||||
|
||||
export function pushSchema(args: RedisCommandArguments, schema: RediSearchSchema) {
|
||||
for (const [field, fieldOptions] of Object.entries(schema)) {
|
||||
args.push(field);
|
||||
|
||||
if (typeof fieldOptions === 'string') {
|
||||
args.push(fieldOptions);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fieldOptions.AS) {
|
||||
args.push('AS', fieldOptions.AS);
|
||||
}
|
||||
|
||||
args.push(fieldOptions.type);
|
||||
|
||||
switch (fieldOptions.type) {
|
||||
case SchemaFieldTypes.TEXT:
|
||||
if (fieldOptions.NOSTEM) {
|
||||
args.push('NOSTEM');
|
||||
}
|
||||
|
||||
if (fieldOptions.WEIGHT) {
|
||||
args.push('WEIGHT', fieldOptions.WEIGHT.toString());
|
||||
}
|
||||
|
||||
if (fieldOptions.PHONETIC) {
|
||||
args.push('PHONETIC', fieldOptions.PHONETIC);
|
||||
}
|
||||
|
||||
if (fieldOptions.WITHSUFFIXTRIE) {
|
||||
args.push('WITHSUFFIXTRIE');
|
||||
}
|
||||
|
||||
pushCommonFieldArguments(args, fieldOptions);
|
||||
|
||||
if (fieldOptions.INDEXEMPTY) {
|
||||
args.push('INDEXEMPTY');
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case SchemaFieldTypes.NUMERIC:
|
||||
case SchemaFieldTypes.GEO:
|
||||
pushCommonFieldArguments(args, fieldOptions);
|
||||
break;
|
||||
|
||||
case SchemaFieldTypes.TAG:
|
||||
if (fieldOptions.SEPARATOR) {
|
||||
args.push('SEPARATOR', fieldOptions.SEPARATOR);
|
||||
}
|
||||
|
||||
if (fieldOptions.CASESENSITIVE) {
|
||||
args.push('CASESENSITIVE');
|
||||
}
|
||||
|
||||
if (fieldOptions.WITHSUFFIXTRIE) {
|
||||
args.push('WITHSUFFIXTRIE');
|
||||
}
|
||||
|
||||
pushCommonFieldArguments(args, fieldOptions);
|
||||
|
||||
if (fieldOptions.INDEXEMPTY) {
|
||||
args.push('INDEXEMPTY');
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case SchemaFieldTypes.VECTOR:
|
||||
args.push(fieldOptions.ALGORITHM);
|
||||
|
||||
pushArgumentsWithLength(args, () => {
|
||||
args.push(
|
||||
'TYPE', fieldOptions.TYPE,
|
||||
'DIM', fieldOptions.DIM.toString(),
|
||||
'DISTANCE_METRIC', fieldOptions.DISTANCE_METRIC
|
||||
);
|
||||
|
||||
if (fieldOptions.INITIAL_CAP) {
|
||||
args.push('INITIAL_CAP', fieldOptions.INITIAL_CAP.toString());
|
||||
}
|
||||
|
||||
switch (fieldOptions.ALGORITHM) {
|
||||
case VectorAlgorithms.FLAT:
|
||||
if (fieldOptions.BLOCK_SIZE) {
|
||||
args.push('BLOCK_SIZE', fieldOptions.BLOCK_SIZE.toString());
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case VectorAlgorithms.HNSW:
|
||||
if (fieldOptions.M) {
|
||||
args.push('M', fieldOptions.M.toString());
|
||||
}
|
||||
|
||||
if (fieldOptions.EF_CONSTRUCTION) {
|
||||
args.push('EF_CONSTRUCTION', fieldOptions.EF_CONSTRUCTION.toString());
|
||||
}
|
||||
|
||||
if (fieldOptions.EF_RUNTIME) {
|
||||
args.push('EF_RUNTIME', fieldOptions.EF_RUNTIME.toString());
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
break;
|
||||
|
||||
case SchemaFieldTypes.GEOSHAPE:
|
||||
if (fieldOptions.COORD_SYSTEM !== undefined) {
|
||||
args.push('COORD_SYSTEM', fieldOptions.COORD_SYSTEM);
|
||||
}
|
||||
|
||||
pushCommonFieldArguments(args, fieldOptions);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (fieldOptions.INDEXMISSING) {
|
||||
args.push('INDEXMISSING');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type Params = Record<string, RedisCommandArgument | number>;
|
||||
|
||||
export function pushParamsArgs(
|
||||
args: RedisCommandArguments,
|
||||
params?: Params
|
||||
): RedisCommandArguments {
|
||||
if (params) {
|
||||
const enrties = Object.entries(params);
|
||||
args.push('PARAMS', (enrties.length * 2).toString());
|
||||
for (const [key, value] of enrties) {
|
||||
args.push(key, typeof value === 'number' ? value.toString() : value);
|
||||
}
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
export function pushSearchOptions(
|
||||
args: RedisCommandArguments,
|
||||
options?: SearchOptions
|
||||
): RedisCommandArguments {
|
||||
if (options?.VERBATIM) {
|
||||
args.push('VERBATIM');
|
||||
}
|
||||
|
||||
if (options?.NOSTOPWORDS) {
|
||||
args.push('NOSTOPWORDS');
|
||||
}
|
||||
|
||||
// if (options?.WITHSCORES) {
|
||||
// args.push('WITHSCORES');
|
||||
// }
|
||||
|
||||
// if (options?.WITHPAYLOADS) {
|
||||
// args.push('WITHPAYLOADS');
|
||||
// }
|
||||
|
||||
pushOptionalVerdictArgument(args, 'INKEYS', options?.INKEYS);
|
||||
pushOptionalVerdictArgument(args, 'INFIELDS', options?.INFIELDS);
|
||||
pushOptionalVerdictArgument(args, 'RETURN', options?.RETURN);
|
||||
|
||||
if (options?.SUMMARIZE) {
|
||||
args.push('SUMMARIZE');
|
||||
|
||||
if (typeof options.SUMMARIZE === 'object') {
|
||||
if (options.SUMMARIZE.FIELDS) {
|
||||
args.push('FIELDS');
|
||||
pushVerdictArgument(args, options.SUMMARIZE.FIELDS);
|
||||
}
|
||||
|
||||
if (options.SUMMARIZE.FRAGS) {
|
||||
args.push('FRAGS', options.SUMMARIZE.FRAGS.toString());
|
||||
}
|
||||
|
||||
if (options.SUMMARIZE.LEN) {
|
||||
args.push('LEN', options.SUMMARIZE.LEN.toString());
|
||||
}
|
||||
|
||||
if (options.SUMMARIZE.SEPARATOR) {
|
||||
args.push('SEPARATOR', options.SUMMARIZE.SEPARATOR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (options?.HIGHLIGHT) {
|
||||
args.push('HIGHLIGHT');
|
||||
|
||||
if (typeof options.HIGHLIGHT === 'object') {
|
||||
if (options.HIGHLIGHT.FIELDS) {
|
||||
args.push('FIELDS');
|
||||
pushVerdictArgument(args, options.HIGHLIGHT.FIELDS);
|
||||
}
|
||||
|
||||
if (options.HIGHLIGHT.TAGS) {
|
||||
args.push('TAGS', options.HIGHLIGHT.TAGS.open, options.HIGHLIGHT.TAGS.close);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (options?.SLOP) {
|
||||
args.push('SLOP', options.SLOP.toString());
|
||||
}
|
||||
|
||||
if (options?.INORDER) {
|
||||
args.push('INORDER');
|
||||
}
|
||||
|
||||
if (options?.LANGUAGE) {
|
||||
args.push('LANGUAGE', options.LANGUAGE);
|
||||
}
|
||||
|
||||
if (options?.EXPANDER) {
|
||||
args.push('EXPANDER', options.EXPANDER);
|
||||
}
|
||||
|
||||
if (options?.SCORER) {
|
||||
args.push('SCORER', options.SCORER);
|
||||
}
|
||||
|
||||
// if (options?.EXPLAINSCORE) {
|
||||
// args.push('EXPLAINSCORE');
|
||||
// }
|
||||
|
||||
// if (options?.PAYLOAD) {
|
||||
// args.push('PAYLOAD', options.PAYLOAD);
|
||||
// }
|
||||
|
||||
if (options?.SORTBY) {
|
||||
args.push('SORTBY');
|
||||
pushSortByProperty(args, options.SORTBY);
|
||||
}
|
||||
|
||||
// if (options?.MSORTBY) {
|
||||
// pushSortByArguments(args, 'MSORTBY', options.MSORTBY);
|
||||
// }
|
||||
|
||||
if (options?.LIMIT) {
|
||||
args.push(
|
||||
'LIMIT',
|
||||
options.LIMIT.from.toString(),
|
||||
options.LIMIT.size.toString()
|
||||
);
|
||||
}
|
||||
|
||||
if (options?.PARAMS) {
|
||||
pushParamsArgs(args, options.PARAMS);
|
||||
}
|
||||
|
||||
if (options?.DIALECT) {
|
||||
args.push('DIALECT', options.DIALECT.toString());
|
||||
}
|
||||
|
||||
if (options?.RETURN?.length === 0) {
|
||||
args.preserve = true;
|
||||
}
|
||||
|
||||
if (options?.TIMEOUT !== undefined) {
|
||||
args.push('TIMEOUT', options.TIMEOUT.toString());
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
interface SearchDocumentValue {
|
||||
[key: string]: string | number | null | Array<SearchDocumentValue> | SearchDocumentValue;
|
||||
}
|
||||
|
||||
export interface SearchReply {
|
||||
total: number;
|
||||
documents: Array<{
|
||||
id: string;
|
||||
value: SearchDocumentValue;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface ProfileOptions {
|
||||
LIMITED?: true;
|
||||
}
|
||||
|
||||
export type ProfileRawReply<T> = [
|
||||
results: T,
|
||||
profile: [
|
||||
_: string,
|
||||
TotalProfileTime: string,
|
||||
_: string,
|
||||
ParsingTime: string,
|
||||
_: string,
|
||||
PipelineCreationTime: string,
|
||||
_: string,
|
||||
IteratorsProfile: Array<any>
|
||||
]
|
||||
];
|
||||
|
||||
export interface ProfileReply {
|
||||
results: SearchReply | AGGREGATE.AggregateReply;
|
||||
profile: ProfileData;
|
||||
}
|
||||
|
||||
interface ChildIterator {
|
||||
type?: string,
|
||||
counter?: number,
|
||||
term?: string,
|
||||
size?: number,
|
||||
time?: string,
|
||||
childIterators?: Array<ChildIterator>
|
||||
}
|
||||
|
||||
interface IteratorsProfile {
|
||||
type?: string,
|
||||
counter?: number,
|
||||
queryType?: string,
|
||||
time?: string,
|
||||
childIterators?: Array<ChildIterator>
|
||||
}
|
||||
|
||||
interface ProfileData {
|
||||
totalProfileTime: string,
|
||||
parsingTime: string,
|
||||
pipelineCreationTime: string,
|
||||
iteratorsProfile: IteratorsProfile
|
||||
}
|
||||
|
||||
export function transformProfile(reply: Array<any>): ProfileData{
|
||||
return {
|
||||
totalProfileTime: reply[0][1],
|
||||
parsingTime: reply[1][1],
|
||||
pipelineCreationTime: reply[2][1],
|
||||
iteratorsProfile: transformIterators(reply[3][1])
|
||||
};
|
||||
}
|
||||
|
||||
function transformIterators(IteratorsProfile: Array<any>): IteratorsProfile {
|
||||
var res: IteratorsProfile = {};
|
||||
for (let i = 0; i < IteratorsProfile.length; i += 2) {
|
||||
const value = IteratorsProfile[i+1];
|
||||
switch (IteratorsProfile[i]) {
|
||||
case 'Type':
|
||||
res.type = value;
|
||||
break;
|
||||
case 'Counter':
|
||||
res.counter = value;
|
||||
break;
|
||||
case 'Time':
|
||||
res.time = value;
|
||||
break;
|
||||
case 'Query type':
|
||||
res.queryType = value;
|
||||
break;
|
||||
case 'Child iterators':
|
||||
res.childIterators = value.map(transformChildIterators);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
function transformChildIterators(IteratorsProfile: Array<any>): ChildIterator {
|
||||
var res: ChildIterator = {};
|
||||
for (let i = 1; i < IteratorsProfile.length; i += 2) {
|
||||
const value = IteratorsProfile[i+1];
|
||||
switch (IteratorsProfile[i]) {
|
||||
case 'Type':
|
||||
res.type = value;
|
||||
break;
|
||||
case 'Counter':
|
||||
res.counter = value;
|
||||
break;
|
||||
case 'Time':
|
||||
res.time = value;
|
||||
break;
|
||||
case 'Size':
|
||||
res.size = value;
|
||||
break;
|
||||
case 'Term':
|
||||
res.term = value;
|
||||
break;
|
||||
case 'Child iterators':
|
||||
res.childIterators = value.map(transformChildIterators);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
Reference in New Issue
Block a user