Vector Search with the JavaScript Client

What You Will Learn

How to store entities with vector embeddings in Infinispan and perform kNN (k-nearest neighbor) vector searches, including hybrid queries that combine vector similarity with full-text search and metadata filters.

Prerequisites

  • Node.js 22+

  • An Infinispan Server running on localhost:11222

  • The protobufjs npm package (installed via npm install)

Start an Infinispan Server with Docker or Podman:

docker run -it --rm -p 11222:11222 -e USER=admin -e PASS=password quay.io/infinispan/server:latest
Tip
You can replace docker with podman in the command above if you use Podman.

Step 1: Define a Protobuf Schema with a Vector Field

Use the @Vector annotation to declare a vector field with its dimension and similarity function. The 3-dimensional vectors represent taste profiles: dark/roasty, light/crisp, and hoppy/craft:

syntax = "proto3";
package quickstart;
/**
 * @TypeId(1000050)
 * @Indexed
 */
message Beer {
    /** @Keyword(projectable = true, sortable = true) */
    string name = 1;
    /** @Keyword(projectable = true, normalizer = "lowercase") */
    string style = 2;
    /** @Keyword(projectable = true, sortable = true, normalizer = "lowercase") */
    string brewery = 3;
    /** @Keyword(projectable = true, normalizer = "lowercase") */
    string country = 4;
    /** @Basic(projectable = true, sortable = true) */
    double abv = 5;
    /** @Text */
    string description = 6;
    /** @Vector(dimension = 3, similarity = COSINE) */
    repeated float descriptionEmbedding = 7;
}

Step 2: Register the Schema and Create an Indexed Cache

Register the schema on the server and create a cache with indexing enabled:

// Register schema
const metaClient = await ispn.client({port, host}, {
    ...authOpts,
    cacheName: '___protobuf_metadata',
    dataFormat: {keyType: 'text/plain', valueType: 'text/plain'}
});
await metaClient.put('quickstart/Beer.proto', beerProto);

// Create indexed cache
const adminClient = await ispn.client({port, host}, {
    ...authOpts,
    clientIntelligence: 'BASIC'
});
const cacheConfig =
  '<distributed-cache>' +
  '<encoding><key media-type="text/plain"/><value media-type="application/x-protostream"/></encoding>' +
  '<indexing enabled="true" storage="filesystem" startup-mode="AUTO">' +
  '<indexed-entities><indexed-entity>quickstart.Beer</indexed-entity></indexed-entities>' +
  '</indexing></distributed-cache>';
await adminClient.admin.getOrCreateCache('beers', cacheConfig);

Step 3: Populate Data with Vector Embeddings

Store beer entries with hand-crafted 3D vectors that cluster similar styles together:

const Beer = root.lookupType('.quickstart.Beer');
const msg = Beer.create({
    name: 'Guinness', style: 'Stout', brewery: 'Guinness Brewery',
    country: 'Ireland', abv: 4.2,
    description: 'A rich, creamy stout with deep roasted barley flavours...',
    descriptionEmbedding: [0.95, 0.05, 0.10]
});
await client.put('beer:1', msg);

Use the <→ operator with ~k to find the k nearest neighbors. Vector values are specified inline in the query:

const results = await client.query({
    queryString: 'from quickstart.Beer b where b.descriptionEmbedding <-> [0.9, 0.1, 0.1]~3'
});

Add a score projection to see how close each match is:

const scored = await client.query({
    queryString: 'select b.name, b.style, score(b) from quickstart.Beer b ' +
        'where b.descriptionEmbedding <-> [0.05, 0.9, 0.1]~3'
});

Step 5: Run Hybrid Queries

Combine vector similarity with metadata filters using the filtering clause:

// Vector + keyword + range filter
const hybrid = await client.query({
    queryString: "select score(b), b.name, b.style, b.abv from quickstart.Beer b " +
        "where b.descriptionEmbedding <-> [0.05, 0.95, 0.05]~3 " +
        "filtering (b.style = 'Lager' and b.abv < 5.0)"
});

// Vector + full-text filter
const textHybrid = await client.query({
    queryString: "select score(b), b.name, b.brewery, b.abv from quickstart.Beer b " +
        "where b.descriptionEmbedding <-> [0.1, 0.1, 0.95]~5 " +
        "filtering b.description : 'citrus'"
});

Step 6: Run the Tutorial

npm run vector-search

The output demonstrates full-text search, keyword/range filters, projections, kNN vector search with score projections, and hybrid queries filtering by style, country, and description text.

What’s Next