diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index df8cb1d1b6..89925b55d4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -22,7 +22,7 @@ jobs: fail-fast: false matrix: node-version: ["18", "20", "22"] - redis-version: ["rs-7.4.0-v1", "8.0.2", "8.2-rc1"] + redis-version: ["rs-7.4.0-v1", "8.0.2", "8.2-rc2-pre"] steps: - uses: actions/checkout@v4 with: diff --git a/package-lock.json b/package-lock.json index 2fda06e6a8..f30ee3a7d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7337,7 +7337,7 @@ }, "packages/bloom": { "name": "@redis/bloom", - "version": "5.7.0", + "version": "5.8.0", "license": "MIT", "devDependencies": { "@redis/test-utils": "*" @@ -7346,12 +7346,12 @@ "node": ">= 18" }, "peerDependencies": { - "@redis/client": "^5.7.0" + "@redis/client": "^5.8.0" } }, "packages/client": { "name": "@redis/client", - "version": "5.7.0", + "version": "5.8.0", "license": "MIT", "dependencies": { "cluster-key-slot": "1.1.2" @@ -7367,7 +7367,7 @@ }, "packages/entraid": { "name": "@redis/entraid", - "version": "5.6.1", + "version": "5.7.0", "license": "MIT", "dependencies": { "@azure/identity": "^4.7.0", @@ -7386,7 +7386,7 @@ "node": ">= 18" }, "peerDependencies": { - "@redis/client": "^5.6.1" + "@redis/client": "^5.7.0" } }, "packages/entraid/node_modules/@types/node": { @@ -7423,7 +7423,7 @@ }, "packages/json": { "name": "@redis/json", - "version": "5.7.0", + "version": "5.8.0", "license": "MIT", "devDependencies": { "@redis/test-utils": "*" @@ -7432,39 +7432,39 @@ "node": ">= 18" }, "peerDependencies": { - "@redis/client": "^5.7.0" + "@redis/client": "^5.8.0" } }, "packages/redis": { - "version": "5.6.1", + "version": "5.7.0", "license": "MIT", "dependencies": { - "@redis/bloom": "5.6.1", - "@redis/client": "5.6.1", - "@redis/json": "5.6.1", - "@redis/search": "5.6.1", - "@redis/time-series": "5.6.1" + "@redis/bloom": "5.7.0", + "@redis/client": "5.7.0", + "@redis/json": "5.7.0", + "@redis/search": "5.7.0", + "@redis/time-series": "5.7.0" }, "engines": { "node": ">= 18" } }, "packages/redis/node_modules/@redis/bloom": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-5.6.1.tgz", - "integrity": "sha512-5/22U76IMEfn6TeZ+uvjXspHw+ykBF0kpBa8xouzeHaQMXs/auqBUOEYzU2VKYDvnd2RSpPTyIg82oB7PpUgLg==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-5.7.0.tgz", + "integrity": "sha512-KtBHDH2Aw1BxYDQd87PJsdEmZcpMbD4oPzdBwB4IvSRmMovukO2NNGi5vpCHhCoicS83zu7cjX1fw79uFBZFJA==", "license": "MIT", "engines": { "node": ">= 18" }, "peerDependencies": { - "@redis/client": "^5.6.1" + "@redis/client": "^5.7.0" } }, "packages/redis/node_modules/@redis/client": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/@redis/client/-/client-5.6.1.tgz", - "integrity": "sha512-bWHmSFIJ5w1Y4aHsYs46XMDHKQsBHFRhNcllYaBxz2Zl+lu+gbm5yI9BqxvKh48bLTs/Wx1Kns0gN2WIasE8MA==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-5.7.0.tgz", + "integrity": "sha512-YV3Knspdj9k6H6s4v8QRcj1WBxHt40vtPmszLKGwRUOUpUOLWSlI9oCUjprMDcQNzgSCXGXYdL/Aj6nT2+Ub0w==", "license": "MIT", "dependencies": { "cluster-key-slot": "1.1.2" @@ -7474,20 +7474,20 @@ } }, "packages/redis/node_modules/@redis/json": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/@redis/json/-/json-5.6.1.tgz", - "integrity": "sha512-cTggVzPIVuiFeXcEcnTRiUzV7rmUvM9KUYxWiHyjsAVACTEUe4ifKkvzrij0H/z3ammU5tfGACffDB3olBwtVA==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-5.7.0.tgz", + "integrity": "sha512-VP3wtse1PSB/UjZAV1lWyDrWrrZcwi/cjb3L0lIarcIJ+EbHliB2QPml0Bvjz8F8F0eDJRtChJVXFc+jhGxCtA==", "license": "MIT", "engines": { "node": ">= 18" }, "peerDependencies": { - "@redis/client": "^5.6.1" + "@redis/client": "^5.7.0" } }, "packages/search": { "name": "@redis/search", - "version": "5.6.1", + "version": "5.7.0", "license": "MIT", "devDependencies": { "@redis/test-utils": "*" @@ -7496,7 +7496,7 @@ "node": ">= 18" }, "peerDependencies": { - "@redis/client": "^5.6.1" + "@redis/client": "^5.7.0" } }, "packages/test-utils": { @@ -7565,7 +7565,7 @@ }, "packages/time-series": { "name": "@redis/time-series", - "version": "5.6.1", + "version": "5.7.0", "license": "MIT", "devDependencies": { "@redis/test-utils": "*" @@ -7574,7 +7574,7 @@ "node": ">= 18" }, "peerDependencies": { - "@redis/client": "^5.6.1" + "@redis/client": "^5.7.0" } } } diff --git a/packages/bloom/lib/test-utils.ts b/packages/bloom/lib/test-utils.ts index 268ebca8cb..0f77acae6f 100644 --- a/packages/bloom/lib/test-utils.ts +++ b/packages/bloom/lib/test-utils.ts @@ -4,7 +4,7 @@ import RedisBloomModules from '.'; export default TestUtils.createFromConfig({ dockerImageName: 'redislabs/client-libs-test', dockerImageVersionArgument: 'redis-version', - defaultDockerVersion: '8.2-rc1' + defaultDockerVersion: '8.2-rc2-pre' }); export const GLOBAL = { diff --git a/packages/bloom/package.json b/packages/bloom/package.json index edc106214e..00af3c1869 100644 --- a/packages/bloom/package.json +++ b/packages/bloom/package.json @@ -1,6 +1,6 @@ { "name": "@redis/bloom", - "version": "5.7.0", + "version": "5.8.0", "license": "MIT", "main": "./dist/lib/index.js", "types": "./dist/lib/index.d.ts", @@ -13,7 +13,7 @@ "release": "release-it" }, "peerDependencies": { - "@redis/client": "^5.7.0" + "@redis/client": "^5.8.0" }, "devDependencies": { "@redis/test-utils": "*" diff --git a/packages/client/lib/client/index.spec.ts b/packages/client/lib/client/index.spec.ts index f04d646706..0aed98450d 100644 --- a/packages/client/lib/client/index.spec.ts +++ b/packages/client/lib/client/index.spec.ts @@ -64,7 +64,8 @@ describe('Client', () => { const expected: RedisClientOptions = { socket: { host: 'localhost', - port: 6379 + port: 6379, + tls: false }, username: 'user', password: 'secret', @@ -161,12 +162,58 @@ describe('Client', () => { { socket: { host: 'localhost', + tls: false } } ); }); }); + describe('parseOptions', () => { + it('should throw error if tls socket option is set to true and the url protocol is "redis:"', () => { + assert.throws( + () => RedisClient.parseOptions({ + url: 'redis://localhost', + socket: { + tls: true + } + }), + TypeError + ); + }); + it('should throw error if tls socket option is set to false and the url protocol is "rediss:"', () => { + assert.throws( + () => RedisClient.parseOptions({ + url: 'rediss://localhost', + socket: { + tls: false + } + }), + TypeError + ); + }); + it('should not throw when tls socket option and url protocol matches"', () => { + assert.equal( + RedisClient.parseOptions({ + url: 'rediss://localhost', + socket: { + tls: true + } + }).socket.tls, + true + ); + assert.equal( + RedisClient.parseOptions({ + url: 'redis://localhost', + socket: { + tls: false + } + }).socket.tls, + false + ); + }); + }); + describe('authentication', () => { testUtils.testWithClient('Client should be authenticated', async client => { assert.equal( diff --git a/packages/client/lib/client/index.ts b/packages/client/lib/client/index.ts index 128dc59967..ccad872e22 100644 --- a/packages/client/lib/client/index.ts +++ b/packages/client/lib/client/index.ts @@ -315,21 +315,45 @@ export default class RedisClient< return RedisClient.factory(options)(options); } - static parseURL(url: string): RedisClientOptions { + static parseOptions(options: O): O { + if (options?.url) { + const parsed = RedisClient.parseURL(options.url); + if (options.socket) { + if (options.socket.tls !== undefined && options.socket.tls !== parsed.socket.tls) { + throw new TypeError(`tls socket option is set to ${options.socket.tls} which is mismatch with protocol or the URL ${options.url} passed`) + } + parsed.socket = Object.assign(options.socket, parsed.socket); + } + + Object.assign(options, parsed); + } + return options; + } + + static parseURL(url: string): RedisClientOptions & { + socket: Exclude & { + tls: boolean + } + } { // https://www.iana.org/assignments/uri-schemes/prov/redis const { hostname, port, protocol, username, password, pathname } = new URL(url), - parsed: RedisClientOptions = { + parsed: RedisClientOptions & { + socket: Exclude & { + tls: boolean + } + } = { socket: { - host: hostname + host: hostname, + tls: false } }; - if (protocol === 'rediss:') { - parsed!.socket!.tls = true; - } else if (protocol !== 'redis:') { + if (protocol !== 'redis:' && protocol !== 'rediss:') { throw new TypeError('Invalid protocol'); } + parsed.socket.tls = protocol === 'rediss:'; + if (port) { (parsed.socket as TcpSocketConnectOpts).port = Number(port); } @@ -464,15 +488,6 @@ export default class RedisClient< }; } - if (options?.url) { - const parsed = RedisClient.parseURL(options.url); - if (options.socket) { - parsed.socket = Object.assign(options.socket, parsed.socket); - } - - Object.assign(options, parsed); - } - if (options?.database) { this._self.#selectedDB = options.database; } @@ -481,6 +496,10 @@ export default class RedisClient< this._commandOptions = options.commandOptions; } + if (options) { + return RedisClient.parseOptions(options); + } + return options; } diff --git a/packages/client/lib/sentinel/test-util.ts b/packages/client/lib/sentinel/test-util.ts index 7c4752a885..a88c981858 100644 --- a/packages/client/lib/sentinel/test-util.ts +++ b/packages/client/lib/sentinel/test-util.ts @@ -174,7 +174,7 @@ export class SentinelFramework extends DockerBase { this.#testUtils = TestUtils.createFromConfig({ dockerImageName: 'redislabs/client-libs-test', dockerImageVersionArgument: 'redis-version', - defaultDockerVersion: '8.2-rc1' + defaultDockerVersion: '8.2-rc2-pre' }); this.#nodeMap = new Map>>>(); this.#sentinelMap = new Map>>>(); diff --git a/packages/client/lib/test-utils.ts b/packages/client/lib/test-utils.ts index 86b6ed294a..62509dee14 100644 --- a/packages/client/lib/test-utils.ts +++ b/packages/client/lib/test-utils.ts @@ -9,7 +9,7 @@ import RedisBloomModules from '@redis/bloom'; const utils = TestUtils.createFromConfig({ dockerImageName: 'redislabs/client-libs-test', dockerImageVersionArgument: 'redis-version', - defaultDockerVersion: '8.2-rc1' + defaultDockerVersion: '8.2-rc2-pre' }); export default utils; diff --git a/packages/client/package.json b/packages/client/package.json index 091ddaf5c9..b7c11d57a8 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@redis/client", - "version": "5.7.0", + "version": "5.8.0", "license": "MIT", "main": "./dist/index.js", "types": "./dist/index.d.ts", diff --git a/packages/entraid/lib/test-utils.ts b/packages/entraid/lib/test-utils.ts index e5d977a6b4..1b63a955bf 100644 --- a/packages/entraid/lib/test-utils.ts +++ b/packages/entraid/lib/test-utils.ts @@ -6,7 +6,7 @@ import { EntraidCredentialsProvider } from './entraid-credentials-provider'; export const testUtils = TestUtils.createFromConfig({ dockerImageName: 'redislabs/client-libs-test', dockerImageVersionArgument: 'redis-version', - defaultDockerVersion: '8.2-rc1' + defaultDockerVersion: '8.2-rc2-pre' }); const DEBUG_MODE_ARGS = testUtils.isVersionGreaterThan([7]) ? diff --git a/packages/entraid/package.json b/packages/entraid/package.json index 4e702fabb6..cb57d9074c 100644 --- a/packages/entraid/package.json +++ b/packages/entraid/package.json @@ -1,6 +1,6 @@ { "name": "@redis/entraid", - "version": "5.6.1", + "version": "5.7.0", "license": "MIT", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -22,7 +22,7 @@ "@azure/msal-node": "^2.16.1" }, "peerDependencies": { - "@redis/client": "^5.6.1" + "@redis/client": "^5.7.0" }, "devDependencies": { "@types/express": "^4.17.21", diff --git a/packages/json/lib/test-utils.ts b/packages/json/lib/test-utils.ts index cba2d95e73..81a546fcd6 100644 --- a/packages/json/lib/test-utils.ts +++ b/packages/json/lib/test-utils.ts @@ -4,7 +4,7 @@ import RedisJSON from '.'; export default TestUtils.createFromConfig({ dockerImageName: 'redislabs/client-libs-test', dockerImageVersionArgument: 'redis-version', - defaultDockerVersion: '8.2-rc1' + defaultDockerVersion: '8.2-rc2-pre' }); export const GLOBAL = { diff --git a/packages/json/package.json b/packages/json/package.json index fac6209c36..bab27df34c 100644 --- a/packages/json/package.json +++ b/packages/json/package.json @@ -1,6 +1,6 @@ { "name": "@redis/json", - "version": "5.7.0", + "version": "5.8.0", "license": "MIT", "main": "./dist/lib/index.js", "types": "./dist/lib/index.d.ts", @@ -13,7 +13,7 @@ "release": "release-it" }, "peerDependencies": { - "@redis/client": "^5.7.0" + "@redis/client": "^5.8.0" }, "devDependencies": { "@redis/test-utils": "*" diff --git a/packages/redis/package.json b/packages/redis/package.json index bdb8666348..a14f6ba10b 100644 --- a/packages/redis/package.json +++ b/packages/redis/package.json @@ -1,7 +1,7 @@ { "name": "redis", "description": "A modern, high performance Redis client", - "version": "5.6.1", + "version": "5.7.0", "license": "MIT", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -13,11 +13,11 @@ "release": "release-it" }, "dependencies": { - "@redis/bloom": "5.6.1", - "@redis/client": "5.6.1", - "@redis/json": "5.6.1", - "@redis/search": "5.6.1", - "@redis/time-series": "5.6.1" + "@redis/bloom": "5.7.0", + "@redis/client": "5.7.0", + "@redis/json": "5.7.0", + "@redis/search": "5.7.0", + "@redis/time-series": "5.7.0" }, "engines": { "node": ">= 18" diff --git a/packages/search/lib/commands/CREATE.spec.ts b/packages/search/lib/commands/CREATE.spec.ts index 2c54d3d023..268421ef35 100644 --- a/packages/search/lib/commands/CREATE.spec.ts +++ b/packages/search/lib/commands/CREATE.spec.ts @@ -1,6 +1,6 @@ import { strict as assert } from 'node:assert'; import testUtils, { GLOBAL } from '../test-utils'; -import CREATE, { SCHEMA_FIELD_TYPE, SCHEMA_TEXT_FIELD_PHONETIC, SCHEMA_VECTOR_FIELD_ALGORITHM, REDISEARCH_LANGUAGE } from './CREATE'; +import CREATE, { SCHEMA_FIELD_TYPE, SCHEMA_TEXT_FIELD_PHONETIC, SCHEMA_VECTOR_FIELD_ALGORITHM, REDISEARCH_LANGUAGE, VAMANA_COMPRESSION_ALGORITHM } from './CREATE'; import { parseArgs } from '@redis/client/lib/commands/generic-transformers'; describe('FT.CREATE', () => { @@ -206,6 +206,33 @@ describe('FT.CREATE', () => { ] ); }); + + it('VAMANA algorithm', () => { + assert.deepEqual( + parseArgs(CREATE, 'index', { + field: { + type: SCHEMA_FIELD_TYPE.VECTOR, + ALGORITHM: SCHEMA_VECTOR_FIELD_ALGORITHM.VAMANA, + TYPE: "FLOAT32", + COMPRESSION: VAMANA_COMPRESSION_ALGORITHM.LVQ8, + DIM: 1024, + DISTANCE_METRIC: 'COSINE', + CONSTRUCTION_WINDOW_SIZE: 300, + GRAPH_MAX_DEGREE: 128, + SEARCH_WINDOW_SIZE: 20, + EPSILON: 0.02, + TRAINING_THRESHOLD: 20480, + REDUCE: 512, + } + }), + [ + 'FT.CREATE', 'index', 'SCHEMA', 'field', 'VECTOR', 'SVS-VAMANA', '20', 'TYPE', + 'FLOAT32', 'DIM', '1024', 'DISTANCE_METRIC', 'COSINE', 'COMPRESSION', 'LVQ8', + 'CONSTRUCTION_WINDOW_SIZE', '300', 'GRAPH_MAX_DEGREE', '128', 'SEARCH_WINDOW_SIZE', '20', + 'EPSILON', '0.02', 'TRAINING_THRESHOLD', '20480', 'REDUCE', '512' + ] + ); + }); }); describe('GEOSHAPE', () => { @@ -556,4 +583,87 @@ describe('FT.CREATE', () => { "OK" ); }, GLOBAL.SERVERS.OPEN); + + testUtils.testWithClientIfVersionWithinRange([[8, 2], 'LATEST'], 'client.ft.create vector svs-vamana', async client => { + assert.equal( + await client.ft.create("index_svs_vamana_min_config", { + field: { + type: SCHEMA_FIELD_TYPE.VECTOR, + ALGORITHM: SCHEMA_VECTOR_FIELD_ALGORITHM.VAMANA, + TYPE: "FLOAT32", + DIM: 768, + DISTANCE_METRIC: 'L2', + }, + }), + "OK" + ); + + assert.equal( + await client.ft.create("index_svs_vamana_no_compression", { + field: { + type: SCHEMA_FIELD_TYPE.VECTOR, + ALGORITHM: SCHEMA_VECTOR_FIELD_ALGORITHM.VAMANA, + TYPE: "FLOAT32", + DIM: 512, + DISTANCE_METRIC: 'L2', + CONSTRUCTION_WINDOW_SIZE: 200, + GRAPH_MAX_DEGREE: 64, + SEARCH_WINDOW_SIZE: 50, + EPSILON: 0.01 + }, + }), + "OK" + ); + + assert.equal( + await client.ft.create("index_svs_vamana_compression", { + field: { + type: SCHEMA_FIELD_TYPE.VECTOR, + ALGORITHM: SCHEMA_VECTOR_FIELD_ALGORITHM.VAMANA, + TYPE: "FLOAT32", + COMPRESSION: VAMANA_COMPRESSION_ALGORITHM.LeanVec4x8, + DIM: 1024, + DISTANCE_METRIC: 'COSINE', + CONSTRUCTION_WINDOW_SIZE: 300, + GRAPH_MAX_DEGREE: 128, + SEARCH_WINDOW_SIZE: 20, + EPSILON: 0.02, + TRAINING_THRESHOLD: 20480, + REDUCE: 512, + }, + }), + "OK" + ); + + assert.equal( + await client.ft.create("index_svs_vamana_float16", { + field: { + type: SCHEMA_FIELD_TYPE.VECTOR, + ALGORITHM: SCHEMA_VECTOR_FIELD_ALGORITHM.VAMANA, + TYPE: "FLOAT16", + DIM: 128, + DISTANCE_METRIC: 'IP', + }, + }), + "OK" + ); + + await assert.rejects( + client.ft.create("index_svs_vamana_invalid_config", { + field: { + type: SCHEMA_FIELD_TYPE.VECTOR, + ALGORITHM: SCHEMA_VECTOR_FIELD_ALGORITHM.VAMANA, + TYPE: "FLOAT32", + DIM: 2, + DISTANCE_METRIC: 'L2', + CONSTRUCTION_WINDOW_SIZE: 200, + GRAPH_MAX_DEGREE: 64, + SEARCH_WINDOW_SIZE: 50, + EPSILON: 0.01, + // TRAINING_THRESHOLD should error without COMPRESSION + TRAINING_THRESHOLD: 2048 + }, + }), + ) + }, GLOBAL.SERVERS.OPEN); }); diff --git a/packages/search/lib/commands/CREATE.ts b/packages/search/lib/commands/CREATE.ts index 9f24a256fa..3f892df40d 100644 --- a/packages/search/lib/commands/CREATE.ts +++ b/packages/search/lib/commands/CREATE.ts @@ -54,7 +54,11 @@ interface SchemaTagField extends SchemaCommonField