Skip to content
Merged
Show file tree
Hide file tree
Changes from 24 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
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
157 changes: 157 additions & 0 deletions node/src/server-modules/GlideFt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/**
* 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.
*
* @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 | DecoderOption,
): Promise<"OK" | null> {
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);

// TagField attributes
if (f.type === "TAG") {
if (f.separator) {
args.push("SEPARATOR", f.separator);
}

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

if (f.type === "VECTOR" && 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);
}
});

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

/**
* @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);
}
113 changes: 113 additions & 0 deletions node/src/server-modules/GlideFtOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/**
* 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;
}

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

/**
* If the field contains a tag field.
*/
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;
};

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

/**
* If the field is a vector field that supports vector search.
*/
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;
}
export type VectorFieldAttributesFlat = VectorFieldAttributes & {
/**
* 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.
*/
algorithm: "FLAT";
};
export type VectorFieldAttributesHnsw = VectorFieldAttributes & {
/**
* 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.
*/
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