Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* Java: Added `FT.SEARCH` ([#2439](https://github.com/valkey-io/valkey-glide/pull/2439))
* Java: Added `FT.AGGREGATE` ([#2466](https://github.com/valkey-io/valkey-glide/pull/2466))
* Java: Added `JSON.SET` and `JSON.GET` ([#2462](https://github.com/valkey-io/valkey-glide/pull/2462))
* Node: Added `FT.CREATE` ([#2501](https://github.com/valkey-io/valkey-glide/pull/2501))
* Java: Added `JSON.ARRINSERT` and `JSON.ARRLEN` ([#2476](https://github.com/valkey-io/valkey-glide/pull/2476))
* Java: Added `JSON.DEL` and `JSON.FORGET` ([#2490](https://github.com/valkey-io/valkey-glide/pull/2490))
* Java: Added `FT.ALIASADD`, `FT.ALIASDEL`, `FT.ALIASUPDATE` ([#2442](https://github.com/valkey-io/valkey-glide/pull/2442))
Expand Down
10 changes: 10 additions & 0 deletions node/DEVELOPER.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,16 @@ To run the integration tests with existing servers, run the following command:
```bash
npm run test -- --cluster-endpoints=localhost:7000 --standalone-endpoints=localhost:6379
# If those endpoints use TLS, add `--tls=true` (applies to both endpoints)
npm run test -- --cluster-endpoints=localhost:7000 --standalone-endpoints=localhost:6379 --tls=true
```
By default, the server_modules tests do not run using `npm run test`. After pointing to a server with JSON and VSS modules setup,
run the following command:
```bash
npm run test-modules
```
### Submodules
Expand Down
2 changes: 2 additions & 0 deletions node/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ export * from "./src/GlideClient";
export * from "./src/GlideClusterClient";
export * from "./src/Logger";
export * from "./src/server-modules/GlideJson";
export * from "./src/server-modules/GlideFt";
export * from "./src/server-modules/GlideFtOptions";
export * from "./src/Transaction";
16 changes: 16 additions & 0 deletions node/npm/glide/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,14 @@ function initialize() {
GlideClusterClient,
GlideClientConfiguration,
GlideJson,
GlideFt,
TextField,
TagField,
NumericField,
VectorField,
VectorFieldAttributesFlat,
VectorFieldAttributesHnsw,
FtCreateOptions,
GlideRecord,
GlideString,
JsonGetOptions,
Expand Down Expand Up @@ -228,6 +236,14 @@ function initialize() {
Decoder,
DecoderOption,
GeoAddOptions,
GlideFt,
TextField,
TagField,
NumericField,
VectorField,
VectorFieldAttributesFlat,
VectorFieldAttributesHnsw,
FtCreateOptions,
GlideRecord,
GlideJson,
GlideString,
Expand Down
3 changes: 2 additions & 1 deletion node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@
"compile-protobuf-files": "cd src && pbjs -t static-module -o ProtobufMessage.js ../../glide-core/src/protobuf/*.proto && pbts -o ProtobufMessage.d.ts ProtobufMessage.js",
"fix-protobuf-file": "replace 'this\\.encode\\(message, writer\\)\\.ldelim' 'this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim' src/ProtobufMessage.js",
"test": "npm run build-test-utils && jest --verbose --runInBand --testPathIgnorePatterns='ServerModules'",
"test-modules": "npm run build-test-utils && jest --verbose --runInBand --testPathPattern='ServerModules'",
"test-minimum": "npm run build-test-utils && jest --verbose --runInBand --testNamePattern='^(.(?!(GlideJson|GlideFt|pubsub|kill)))*$'",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

small nit: with a negative lookahead removing the modules/pubsub/kill tests, would it be better to name this something like base rather than minimum?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like base. I originally called it 'fast'. The idea is that it skips the long tests, and could maybe be used for doing a sanity check on the client.

"test-modules": "npm run build-test-utils && jest --verbose --runInBand --testNamePattern='(GlideJson|GlideFt)'",
"build-test-utils": "cd ../utils && npm i && npm run build",
"lint:fix": "npm run install-linting && npx eslint -c ../eslint.config.mjs --fix && npm run prettier:format",
"lint": "npm run install-linting && npx eslint -c ../eslint.config.mjs && npm run prettier:check:ci",
Expand Down
176 changes: 176 additions & 0 deletions node/src/server-modules/GlideFt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
/**
* Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0
*/

import { Decoder, DecoderOption, GlideString } from "../BaseClient";
import { GlideClient } from "../GlideClient";
import { GlideClusterClient } from "../GlideClusterClient";
import { Field, FtCreateOptions } from "./GlideFtOptions";

/** Module for Vector Search commands */
export class GlideFt {
/**
* Creates an index and initiates a backfill of that index.
*
* @param client The client to execute the command.
* @param indexName The index name for the index to be created.
* @param schema The fields of the index schema, specifying the fields and their types.
* @param options Optional arguments for the `FT.CREATE` command. See {@link FtCreateOptions}.
*
* @returns If the index is successfully created, returns "OK".
*
* @example
* ```typescript
* // Example usage of FT.CREATE to create a 6-dimensional JSON index using the HNSW algorithm
* await GlideFt.create(client, "json_idx1", [{
* type: "VECTOR",
* name: "$.vec",
* alias: "VEC",
* attributes: {
* algorithm: "HNSW",
* type: "FLOAT32",
* dimension: 6,
* distanceMetric: "L2",
* numberOfEdges: 32,
* },
* }], {
* dataType: "JSON",
* prefixes: ["json:"]
* });
* ```
*/
static async create(
client: GlideClient | GlideClusterClient,
indexName: GlideString,
schema: Field[],
options?: FtCreateOptions,
): Promise<"OK"> {
const args: GlideString[] = ["FT.CREATE", indexName];

if (options) {
if ("dataType" in options) {
args.push("ON", options.dataType);
}

if ("prefixes" in options && options.prefixes) {
args.push(
"PREFIX",
options.prefixes.length.toString(),
...options.prefixes,
);
}
}

args.push("SCHEMA");

schema.forEach((f) => {
args.push(f.name);

if (f.alias) {
args.push("AS", f.alias);
}

args.push(f.type);

switch (f.type) {
case "TAG": {
if (f.separator) {
args.push("SEPARATOR", f.separator);
}

if (f.caseSensitive) {
args.push("CASESENSITIVE");
}

break;
}

case "VECTOR": {
if (f.attributes) {
args.push(f.attributes.algorithm);

const attributes: GlideString[] = [];

// all VectorFieldAttributes attributes
if (f.attributes.dimension) {
attributes.push(
"DIM",
f.attributes.dimension.toString(),
);
}

if (f.attributes.distanceMetric) {
attributes.push(
"DISTANCE_METRIC",
f.attributes.distanceMetric.toString(),
);
}

if (f.attributes.type) {
attributes.push(
"TYPE",
f.attributes.type.toString(),
);
}

if (f.attributes.initialCap) {
attributes.push(
"INITIAL_CAP",
f.attributes.initialCap.toString(),
);
}

// VectorFieldAttributesHnsw attributes
if ("m" in f.attributes && f.attributes.m) {
attributes.push("M", f.attributes.m.toString());
}

if (
"efContruction" in f.attributes &&
f.attributes.efContruction
) {
attributes.push(
"EF_CONSTRUCTION",
f.attributes.efContruction.toString(),
);
}

if (
"efRuntime" in f.attributes &&
f.attributes.efRuntime
) {
attributes.push(
"EF_RUNTIME",
f.attributes.efRuntime.toString(),
);
}

args.push(attributes.length.toString(), ...attributes);
}

break;
}

default:
// no-op
}
});

return _handleCustomCommand(client, args, {
decoder: Decoder.String,
}) as Promise<"OK">;
}
}

/**
* @internal
*/
function _handleCustomCommand(
client: GlideClient | GlideClusterClient,
args: GlideString[],
decoderOption: DecoderOption,
) {
return client instanceof GlideClient
? (client as GlideClient).customCommand(args, decoderOption)
: (client as GlideClusterClient).customCommand(args, decoderOption);
}
120 changes: 120 additions & 0 deletions node/src/server-modules/GlideFtOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/**
* Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0
*/

import { GlideString } from "../BaseClient";

interface BaseField {
/** The name of the field. */
name: GlideString;
/** An alias for field. */
alias?: GlideString;
}

/**
* Field contains any blob of data.
*/
export type TextField = BaseField & {
/** Field identifier */
type: "TEXT";
};

/**
* Tag fields are similar to full-text fields, but they interpret the text as a simple list of
* tags delimited by a separator character.
*
* For HASH fields, separator default is a comma (`,`). For JSON fields, there is no default
* separator; you must declare one explicitly if needed.
*/
export type TagField = BaseField & {
/** Field identifier */
type: "TAG";
/** Specify how text in the attribute is split into individual tags. Must be a single character. */
separator?: GlideString;
/** Preserve the original letter cases of tags. If set to False, characters are converted to lowercase by default. */
caseSensitive?: boolean;
};

/**
* Field contains a number.
*/
export type NumericField = BaseField & {
/** Field identifier */
type: "NUMERIC";
};

/**
* Superclass for vector field implementations, contains common logic.
*/
export type VectorField = BaseField & {
/** Field identifier */
type: "VECTOR";
/** Additional attributes to be passed with the vector field after the algorithm name. */
attributes: VectorFieldAttributesFlat | VectorFieldAttributesHnsw;
};

/**
* Base class for defining vector field attributes to be used after the vector algorithm name.
*/
export interface VectorFieldAttributes {
/** Number of dimensions in the vector. Equivalent to DIM in the option. */
dimension: number;
/**
* The distance metric used in vector type field. Can be one of [L2 | IP | COSINE].
*/
distanceMetric: "L2" | "IP" | "COSINE";
/** Vector type. The only supported type is FLOAT32. */
type: "FLOAT32";
/**
* Initial vector capacity in the index affecting memory allocation size of the index. Defaults to 1024.
*/
initialCap?: number;
}

/**
* Vector field that supports vector search by FLAT (brute force) algorithm.
*
* The algorithm is a brute force linear processing of each vector in the index, yielding exact
* answers within the bounds of the precision of the distance computations.
*/
export type VectorFieldAttributesFlat = VectorFieldAttributes & {
algorithm: "FLAT";
};

/**
* Vector field that supports vector search by HNSM (Hierarchical Navigable Small World) algorithm.
*
* The algorithm provides an approximation of the correct answer in exchange for substantially
* lower execution times.
*/
export type VectorFieldAttributesHnsw = VectorFieldAttributes & {
algorithm: "HNSW";
/**
* Number of maximum allowed outgoing edges for each node in the graph in each layer. Default is 16, maximum is 512.
* Equivalent to the `m` attribute.
*/
numberOfEdges?: number;
/**
* Controls the number of vectors examined during index construction. Default value is 200, Maximum value is 4096.
* Equivalent to the `efContruction` attribute.
*/
vectorsExaminedOnConstruction?: number;
/**
* Controls the number of vectors examined during query operations. Default value is 10, Maximum value is 4096.
* Equivalent to the `efRuntime` attribute.
*/
vectorsExaminedOnRuntime?: number;
};

export type Field = TextField | TagField | NumericField | VectorField;

/**
* Represents the input options to be used in the FT.CREATE command.
* All fields in this class are optional inputs for FT.CREATE.
*/
export interface FtCreateOptions {
/** The type of data to be indexed using FT.CREATE. */
dataType: "JSON" | "HASH";
/** The prefix of the key to be indexed. */
prefixes?: GlideString[];
}
Loading