From 728309710b413d0b2a5cb9765c20fbbb8084ea51 Mon Sep 17 00:00:00 2001 From: Nikolay Karadzhov Date: Wed, 22 Jul 2026 13:13:00 +0300 Subject: [PATCH 1/3] feat(search): add FT.ALIASLIST command Add the FT.ALIASLIST command to @redis/search. Given an index name, it returns all aliases currently associated with that index, making alias discovery a first-class read-only API instead of relying on FT.INFO. The command is read-only and keyless. The reply is an unordered collection of alias strings: RESP2 array, RESP3 set. An existing index with no aliases yields an empty collection. The client adds no validation and propagates server errors (SEARCH_INDEX_NOT_FOUND, NOPERM) unchanged. Available from RediSearch 8.10.0. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../search/lib/commands/ALIASLIST.spec.ts | 40 +++++++++++++++++++ packages/search/lib/commands/ALIASLIST.ts | 14 +++++++ packages/search/lib/commands/index.ts | 11 +++++ 3 files changed, 65 insertions(+) create mode 100644 packages/search/lib/commands/ALIASLIST.spec.ts create mode 100644 packages/search/lib/commands/ALIASLIST.ts diff --git a/packages/search/lib/commands/ALIASLIST.spec.ts b/packages/search/lib/commands/ALIASLIST.spec.ts new file mode 100644 index 00000000000..086505e8a82 --- /dev/null +++ b/packages/search/lib/commands/ALIASLIST.spec.ts @@ -0,0 +1,40 @@ +import { strict as assert } from 'node:assert'; +import testUtils, { GLOBAL } from '../test-utils'; +import ALIASLIST from './ALIASLIST'; +import { SCHEMA_FIELD_TYPE } from './CREATE'; +import { parseArgs } from '@redis/client/lib/commands/generic-transformers'; + +describe('FT.ALIASLIST', () => { + it('transformArguments', () => { + assert.deepEqual( + parseArgs(ALIASLIST, 'index'), + ['FT.ALIASLIST', 'index'] + ); + }); + + testUtils.testWithClient('client.ft.aliasList on index without aliases', async client => { + await client.ft.create('index', { + field: SCHEMA_FIELD_TYPE.TEXT + }); + + assert.deepEqual( + await client.ft.aliasList('index'), + [] + ); + }, GLOBAL.SERVERS.OPEN); + + testUtils.testWithClient('client.ft.aliasList with aliases', async client => { + await client.ft.create('index', { + field: SCHEMA_FIELD_TYPE.TEXT + }); + await client.ft.aliasAdd('alias1', 'index'); + await client.ft.aliasAdd('alias2', 'index'); + + const reply = await client.ft.aliasList('index'); + // ordering is not part of the contract + assert.deepEqual( + [...reply].sort(), + ['alias1', 'alias2'] + ); + }, GLOBAL.SERVERS.OPEN); +}); diff --git a/packages/search/lib/commands/ALIASLIST.ts b/packages/search/lib/commands/ALIASLIST.ts new file mode 100644 index 00000000000..b67bea8687d --- /dev/null +++ b/packages/search/lib/commands/ALIASLIST.ts @@ -0,0 +1,14 @@ +import { CommandParser } from '@redis/client/dist/lib/client/parser'; +import { RedisArgument, ArrayReply, SetReply, BlobStringReply, Command } from '@redis/client/dist/lib/RESP/types'; + +export default { + NOT_KEYED_COMMAND: true, + IS_READ_ONLY: true, + parseCommand(parser: CommandParser, index: RedisArgument) { + parser.push('FT.ALIASLIST', index); + }, + transformReply: { + 2: undefined as unknown as () => ArrayReply, + 3: undefined as unknown as () => SetReply + } +} as const satisfies Command; diff --git a/packages/search/lib/commands/index.ts b/packages/search/lib/commands/index.ts index 20e03f74c80..c10a17aa963 100644 --- a/packages/search/lib/commands/index.ts +++ b/packages/search/lib/commands/index.ts @@ -4,6 +4,7 @@ import AGGREGATE_WITHCURSOR from './AGGREGATE_WITHCURSOR'; import AGGREGATE from './AGGREGATE'; import ALIASADD from './ALIASADD'; import ALIASDEL from './ALIASDEL'; +import ALIASLIST from './ALIASLIST'; import ALIASUPDATE from './ALIASUPDATE'; import CONFIG_GET from './CONFIG_GET'; import CONFIG_SET from './CONFIG_SET'; @@ -121,6 +122,16 @@ export default { * @param alias - The alias to remove */ aliasDel: ALIASDEL, + /** + * Lists all aliases associated with the given index. Read-only; available from RediSearch 8.10.0. + * @param index - The index name (must be an index created with FT.CREATE, not an alias) + */ + ALIASLIST, + /** + * Lists all aliases associated with the given index. Read-only; available from RediSearch 8.10.0. + * @param index - The index name (must be an index created with FT.CREATE, not an alias) + */ + aliasList: ALIASLIST, /** * Updates the index pointed to by an existing alias. * @param alias - The existing alias to update From 8d6a9c125f4d186e6ef91a5bd62e56503bc0e5c4 Mon Sep 17 00:00:00 2001 From: Nikolay Karadzhov Date: Wed, 22 Jul 2026 13:22:01 +0300 Subject: [PATCH 2/3] test(search): cover FT.ALIASLIST index-not-found error Add a rejection test asserting the SEARCH_INDEX_NOT_FOUND error prefix is propagated unchanged for a missing index, per the command's error contract. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/search/lib/commands/ALIASLIST.spec.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/search/lib/commands/ALIASLIST.spec.ts b/packages/search/lib/commands/ALIASLIST.spec.ts index 086505e8a82..ab1129e54b8 100644 --- a/packages/search/lib/commands/ALIASLIST.spec.ts +++ b/packages/search/lib/commands/ALIASLIST.spec.ts @@ -37,4 +37,18 @@ describe('FT.ALIASLIST', () => { ['alias1', 'alias2'] ); }, GLOBAL.SERVERS.OPEN); + + testUtils.testWithClient('client.ft.aliasList on missing index rejects with index-not-found', async client => { + await assert.rejects( + client.ft.aliasList('nonexistent'), + err => { + assert.ok(err instanceof Error); + assert.ok( + err.message.startsWith('SEARCH_INDEX_NOT_FOUND'), + `expected SEARCH_INDEX_NOT_FOUND prefix, got: ${err.message}` + ); + return true; + } + ); + }, GLOBAL.SERVERS.OPEN); }); From b1dec9478a3d575fc164747cb00255e13c981cf7 Mon Sep 17 00:00:00 2001 From: Nikolay Karadzhov Date: Wed, 22 Jul 2026 13:47:06 +0300 Subject: [PATCH 3/3] fix(search): version-gate and align FT.ALIASLIST spec and JSDoc Gate the FT.ALIASLIST integration tests with minimumDockerVersion [8, 10] so pre-8.10 CI matrix tags skip instead of failing with 'unknown command'. Pipeline test setup via Promise.all to match the sibling alias-command specs, and drop the non-conventional 'Read-only'/version prose from the registry JSDoc. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../search/lib/commands/ALIASLIST.spec.ts | 34 ++++++++++--------- packages/search/lib/commands/index.ts | 8 ++--- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/packages/search/lib/commands/ALIASLIST.spec.ts b/packages/search/lib/commands/ALIASLIST.spec.ts index ab1129e54b8..bf4e821222e 100644 --- a/packages/search/lib/commands/ALIASLIST.spec.ts +++ b/packages/search/lib/commands/ALIASLIST.spec.ts @@ -13,30 +13,32 @@ describe('FT.ALIASLIST', () => { }); testUtils.testWithClient('client.ft.aliasList on index without aliases', async client => { - await client.ft.create('index', { - field: SCHEMA_FIELD_TYPE.TEXT - }); + const [, reply] = await Promise.all([ + client.ft.create('index', { + field: SCHEMA_FIELD_TYPE.TEXT + }), + client.ft.aliasList('index') + ]); - assert.deepEqual( - await client.ft.aliasList('index'), - [] - ); - }, GLOBAL.SERVERS.OPEN); + assert.deepEqual(reply, []); + }, { ...GLOBAL.SERVERS.OPEN, minimumDockerVersion: [8, 10] }); testUtils.testWithClient('client.ft.aliasList with aliases', async client => { - await client.ft.create('index', { - field: SCHEMA_FIELD_TYPE.TEXT - }); - await client.ft.aliasAdd('alias1', 'index'); - await client.ft.aliasAdd('alias2', 'index'); + const [, , , reply] = await Promise.all([ + client.ft.create('index', { + field: SCHEMA_FIELD_TYPE.TEXT + }), + client.ft.aliasAdd('alias1', 'index'), + client.ft.aliasAdd('alias2', 'index'), + client.ft.aliasList('index') + ]); - const reply = await client.ft.aliasList('index'); // ordering is not part of the contract assert.deepEqual( [...reply].sort(), ['alias1', 'alias2'] ); - }, GLOBAL.SERVERS.OPEN); + }, { ...GLOBAL.SERVERS.OPEN, minimumDockerVersion: [8, 10] }); testUtils.testWithClient('client.ft.aliasList on missing index rejects with index-not-found', async client => { await assert.rejects( @@ -50,5 +52,5 @@ describe('FT.ALIASLIST', () => { return true; } ); - }, GLOBAL.SERVERS.OPEN); + }, { ...GLOBAL.SERVERS.OPEN, minimumDockerVersion: [8, 10] }); }); diff --git a/packages/search/lib/commands/index.ts b/packages/search/lib/commands/index.ts index c10a17aa963..d1e9cf63b4e 100644 --- a/packages/search/lib/commands/index.ts +++ b/packages/search/lib/commands/index.ts @@ -123,13 +123,13 @@ export default { */ aliasDel: ALIASDEL, /** - * Lists all aliases associated with the given index. Read-only; available from RediSearch 8.10.0. - * @param index - The index name (must be an index created with FT.CREATE, not an alias) + * Lists all aliases associated with the given index. + * @param index - The index name whose aliases to list */ ALIASLIST, /** - * Lists all aliases associated with the given index. Read-only; available from RediSearch 8.10.0. - * @param index - The index name (must be an index created with FT.CREATE, not an alias) + * Lists all aliases associated with the given index. + * @param index - The index name whose aliases to list */ aliasList: ALIASLIST, /**