Hot Rod JS clients provide asynchronous, event-driven access to Infinispan clusters for Node.js applications. The asynchronous operation results are represented with Promise instances, which allows the client to chain multiple invocations and centralize error handling.

1. Installing and configuring Hot Rod JS clients

Ensure your system meets requirements before installing the Hot Rod JS client. You can then configure Hot Rod JS clients to connect to Infinispan Server, use different media types for keys and values, and customize logging.

1.1. Installing Hot Rod JS clients

Infinispan provides a distribution of the Hot Rod JS client that you can install via the NPM package manager.

Prerequisites
  • Node.js version 22 or 24.

  • Infinispan Server 14.x or later.

Procedure
  • Install the infinispan client as follows:

    npm install infinispan

1.2. Configuring Infinispan connections

Configure Hot Rod JS clients to connect to Infinispan Server.

If you add multiple server addresses to the configuration, the Hot Rod JS client loops through them until it finds a node to which it can connect.

However, you only need to add one Infinispan Server address for the client to receive the entire cluster topology. If the Hot Rod JS client connects to a single server instance that is a member of a cluster, the client gets the address information for all nodes.

Because Hot Rod JS clients are topology aware, if a connection to one Infinispan Server breaks, the client retries any incomplete operations on other nodes in the cluster. Likewise, if client listener that is registered on one Infinispan Server fails or leaves the cluster, the client transparently migrates the listener registration to another node in the cluster so that it can continue receiving events.

Prerequisites
  • Install the Hot Rod JS client.

  • Have at least one running Infinispan Server instance.

Procedure
  • Specify hostnames and ports for Infinispan Server in the client configuration.

    var infinispan = require('infinispan');
    
    var connected = infinispan.client(
      [{port: 11322, host: '127.0.0.1'}, {port: 11222, host: '127.0.0.1'}]
      {
        // Configure client connections with authentication and encryption here.
      }
    );
    
    connected.then(function (client) {
    
      var members = client.getTopologyInfo().getMembers();
    
      // Displays all members of the Infinispan cluster.
      console.log('Connected to: ' + JSON.stringify(members));
    
      return client.disconnect();
    
    }).catch(function(error) {
    
      console.log("Got error: " + error.message);
    
    });

1.2.1. Configuring connections with URIs

Configure Hot Rod JS clients to connect to Infinispan Server using Hot Rod URIs.

Hot Rod URIs encode the server address, credentials, and connection parameters in a single string, providing a convenient alternative to the object-based configuration.

URI format
hotrod://[username:password@]host1[:port1][,host2[:port2]][?param=value&...]
hotrods://[username:password@]host1[:port1][,host2[:port2]][?param=value&...]

Use hotrod:// for plain connections and hotrods:// to enable TLS. If you do not specify a port, the default port 11222 is used.

When you include credentials in the URI, the client automatically enables authentication with the PLAIN SASL mechanism. You can override the mechanism with the sasl_mechanism query parameter or the options argument.

Precedence

If you pass both a URI and an options object, the programmatic options take precedence over the URI values. This allows you to set a base configuration in the URI and override specific fields as needed.

Query parameters

The following table lists the query parameters that you can use in Hot Rod URIs.

Parameter Alias Description

sasl_mechanism

SASL authentication mechanism, e.g. SCRAM-SHA-256.

trust_store_file_name

trust_ca

Path to a CA certificate file for TLS verification.

key_store_file_name

client_cert

Path to a client certificate file for mutual TLS.

key_store_password

client_key

Path to a client private key file for mutual TLS.

sni_host_name

sni_host

Server Name Indication (SNI) hostname for TLS.

max_retries

Maximum number of operation retries.

cache_name

Name of the cache to connect to.

Prerequisites
  • Install the Hot Rod JS client.

  • Have at least one running Infinispan Server instance.

Procedure
  1. Connect to a single server with credentials.

    var infinispan = require('infinispan');
    
    // Connect using a Hot Rod URI
    var connected = infinispan.client(
      'hotrod://admin:password@127.0.0.1:11222'
    );
    
    connected.then(function (client) {
    
      console.log('Connected to Infinispan via URI.');
    
      return client.disconnect();
    
    }).catch(function(error) {
    
      console.log("Got error: " + error.message);
    
    });
  2. Connect to multiple servers by separating addresses with commas.

    var infinispan = require('infinispan');
    
    // Connect to multiple servers using comma-separated hosts
    var connected = infinispan.client(
      'hotrod://admin:password@server1:11222,server2:11322,server3:11422'
    );
    
    connected.then(function (client) {
    
      var members = client.getTopologyInfo().getMembers();
      console.log('Connected to: ' + JSON.stringify(members));
    
      return client.disconnect();
    
    }).catch(function(error) {
    
      console.log("Got error: " + error.message);
    
    });
  3. Enable TLS with hotrods:// and add certificate parameters.

    var infinispan = require('infinispan');
    
    // Use hotrods:// for TLS connections with certificate verification
    var connected = infinispan.client(
      'hotrods://admin:password@127.0.0.1:11222?trust_ca=/path/to/ca.pem&sni_host=myserver'
    );
    
    connected.then(function (client) {
    
      console.log('Connected to Infinispan via TLS URI.');
    
      return client.disconnect();
    
    }).catch(function(error) {
    
      console.log("Got error: " + error.message);
    
    });
  4. Override URI settings with the options argument.

    var infinispan = require('infinispan');
    
    // URI provides the base configuration; the options argument overrides specific fields
    var connected = infinispan.client(
      'hotrod://admin:password@127.0.0.1:11222',
      {
        authentication: {
          saslMechanism: 'SCRAM-SHA-256'
        },
        nearCache: {
          maxEntries: 100
        }
      }
    );
    
    connected.then(function (client) {
    
      console.log('Connected with SCRAM-SHA-256 and near caching.');
    
      return client.disconnect();
    
    }).catch(function(error) {
    
      console.log("Got error: " + error.message);
    
    });
URL encoding

If your username or password contains special characters such as @ or :, you must URL-encode them. For example, a user admin@corp with password p@ss becomes:

hotrod://admin%40corp:p%40ss@localhost:11222

1.2.2. Defining Infinispan clusters in client configuration

When you set up Infinispan clusters in separate data centers to perform cross-site replication, you can add connection details for the different sites to the client configuration.

Prerequisites
  • Install the Hot Rod JS client.

  • Configure Infinispan for cross-site replication.

Procedure
  1. Add a clusters definition to your configuration.

  2. Add name and servers definitions for each Infinispan cluster.

    var connected = infinispan.client({port: 11222, host: '127.0.0.1'},
      {
        clusters: [
          {
            name: 'LON',
            servers: [{port: 11222, host: 'LON-host'}]
          },
          {
            name: 'NYC',
            servers: [{port: 11222, host: 'NYC-host1'}, {port: 11322, host: 'NYC-host2'}]
          }
        ]
      });

1.2.3. Manually switching Infinispan clusters

Change the Infinispan cluster to which the Hot Rod JS client is connectioned.

Prerequisites
  • Define Infinispan clusters in the Hot Rod JS client configuration.

Procedure
  1. Call the switchToCluster(clusterName) method to force the client to switch to a Infinispan cluster that is defined in the client configuration.

  2. Call the switchToDefaultCluster() method to start using the initial Infinispan cluster.

    var connected = infinispan.client({port: 11222, host: '127.0.0.1'},
      {
        clusters: [
          {
            name: 'LON',
            servers: [{port: 11222, host: 'LON-host'}]
          },
          {
            name: 'NYC',
            servers: [{port: 11222, host: 'NYC-host1'}, {port: 11322, host: 'NYC-host2'}]
          }
        ]
      });
    
    connected.then(function (client) {
    
      var switchToB = client.getTopologyInfo().switchToCluster('NYC');
    
      switchToB.then(function(switchSucceed) {
    
        if (switchSucceed) {
          // ...
        }
    
        // ...
    
        var switchToDefault = client.getTopologyInfo().switchToDefaultCluster();
    
        switchToDefault.then(function(switchSucceed) {
    
          if (switchSucceed) {
            // ...
          }
    
        })
    
      })
    
    });

1.3. Configuring authentication

Infinispan Server uses different SASL mechanisms to authenticate Hot Rod JS client connections.

Prerequisites
  • Create Infinispan users.

  • Add the SASL authentication mechanism to the Hot Rod connector in your Infinispan Server configuration.

Procedure
  1. Open the Hot Rod JS client configuration for editing.

  2. Add an authentication method that sets the enabled: true flag.

  3. Specify a value for the saslMechanism parameter that matches the SASL authentication mechanism for the Hot Rod connector.

  4. Configure any parameters specific to the SASL authentication mechanism as appropriate.

1.3.1. SASL authentication mechanisms

Hot Rod JS clients can use the following SASL authentication mechanisms to connect to Infinispan Server.

PLAIN

Sends credentials in plain text (unencrypted) over the wire in a way that is similar to HTTP BASIC authentication.

To secure Infinispan credentials, you should use PLAIN authentication only in combination with TLS encryption.

var connected = infinispan.client(
  {port: 11222, host: '127.0.0.1'},
  {
    authentication: {
      enabled: true,
      saslMechanism: 'PLAIN',
      userName: 'username',
      password: 'changeme'
    }
  }
);
DIGEST-MD5

Uses the MD5 hashing algorithm in addition to nonces to encrypt credentials.

var connected = infinispan.client(
  {port: 11222, host: '127.0.0.1'},
  {
    authentication: {
      enabled: true,
      saslMechanism: 'DIGEST-MD5',
      userName: 'username',
      password: 'changeme',
      serverName: 'infinispan'
    }
  }
);
SCRAM

Uses salt values in addition to hashing algorithms and nonce values to encrypt credentials. Hot Rod endpoints support SCRAM-SHA-1, SCRAM-SHA-256, SCRAM-SHA-384, SCRAM-SHA-512 hashing algorithms, in order of strength.

var connected = infinispan.client(
  {port: 11222, host: '127.0.0.1'},
  {
    authentication: {
      enabled: true,
      saslMechanism: 'SCRAM-SHA-1',
      userName: 'username',
      password: 'changeme'
    }
  }
);
EXTERNAL

Uses client certificates to provide valid identities to Infinispan Server and enable encryption.

var connected = infinispan.client(
  {port: 11222, host: '127.0.0.1'},
  {
    authentication: {
      enabled: true,
      saslMechanism: 'EXTERNAL'
    },
    ssl: {
      enabled: true,
      clientAuth: {
        cert: 'out/ssl/client/client.p12',
      }
    }
  }
);
OAUTHBEARER

Uses tokens obtained via an OAuth 2.0 provider to securely connect to Infinispan Server.

var connected = infinispan.client(
  {port: 11222, host: '127.0.0.1'},
  {
    authentication: {
      enabled: true,
      saslMechanism: 'OAUTHBEARER',
      token: `<token>`
    }
  }
);

1.4. Configuring encryption

Infinispan Server can enforce different types of SSL/TLS encryption to secure Hot Rod JS client connections.

Prerequisites
  • Create a trust store that Hot Rod JS clients can use to verify Infinispan Server identities.

  • If you configure Infinispan Server to validate or authenticate client certificates, create a keystore as appropriate.

Procedure
  1. Open the Hot Rod JS client configuration for editing.

  2. Add an ssl method that sets the enabled: true flag.

  3. Provide any other configuration specific to the type of encryption you use.

1.4.1. Encryption types

Hot Rod JS clients can use different types of encryption to negotiate secure connections with Infinispan Server.

Infinispan Server identities

For basic encryption, you can add the signing certificate, or CA bundle, for Infinispan Server certificates to your configuration as follows:

To verify certificates issued to Infinispan Server, Hot Rod JS clients require either the full certificate chain or a partial chain that starts with the Root CA.

var connected = infinispan.client({port: 11222, host: '127.0.0.1'},
  {
    ssl: {
      enabled: true,
      trustCerts: ['my-root-ca.crt.pem']
    }
  }
);
Trust stores

You can add trust stores in PKCS12 or PFX format as follows:

var connected = infinispan.client({port: 11222, host: '127.0.0.1'},
  {
    ssl: {
      enabled: true,
      cryptoStore: {
        path: 'my-truststore.p12',
        passphrase: 'secret'
      }
    }
  }
);
Client certificate authentication

If you enable client certificate authentication in Infinispan Server configuration, add a keystore as in the following example:

You must configure the Hot Rod JS client with the EXTERNAL authentication mechanism when using client certificate authentication.

var connected = infinispan.client({port: 11222, host: '127.0.0.1'},
  {
    ssl: {
      enabled: true,
      trustCerts: ['my-root-ca.crt.pem'],
      clientAuth: {
        key: 'privkey.pem',
        passphrase: 'secret',
        cert: 'cert.pem'
      }
    }
  }
);
Server Name Indication (SNI)

If you use SNI to allow Hot Rod JS clients to request Infinispan Server hostnames, set a value for the sniHostName parameter that matches a hostname in the Infinispan Server configuration.

The sniHostName parameter defaults to localhost.

var connected = infinispan.client({port: 11222, host: '127.0.0.1'},
  {
    ssl: {
      enabled: true,
      trustCerts: ['my-root-ca.crt.pem']
      sniHostName: 'example.com'
    }
  }
);

Hot Rod JS clients do not allow self-signed certificates by default, which can cause issues in development or test environments where no public certificate authority (CA) key is available.

Check out the Infinispan code tutorials for an example on creating signed certificates with the Java keytool.

1.5. Configuring data formats

Hot Rod JS clients can handle keys and values as native JavaScript Object Notation (JSON) objects or as String objects. By default, clients handle entries as String objects. If you want to transmit data to Infinispan Server in JSON format, then you must configure the Hot Rod JS client.

Starting at version 0.6, Hot Rod JS clients support native JSON objects as keys and values. Earlier versions of the client support keys and values as String only.

Script operations support String key/value pairs and String parameters only.

Procedure
  1. Add a dataFormat configuration to your client.

  2. Set the data format for keys and values as appropriate with the keyType and valueType parameters.

Keys and values can have different media types. For JSON objects, specify application/json. For String objects, specify text/plain or omit the parameter to use the default.

var infinispan = require('infinispan');

var connected = infinispan.client(
    {port: 11222, host: '127.0.0.1'},
    {
        dataFormat : {
            keyType: 'application/json',
            valueType: 'application/json'
        }
    }
);

connected.then(function (client) {

  var clientPut = client.put({k: 'key'}, {v: 'value'});

  var clientGet = clientPut.then(
      function() { return client.get({k: 'key'}); });

  var showGet = clientGet.then(
      function(value) { console.log("get({k: 'key'})=" + JSON.stringify(value)); });

  return showGet.finally(
      function() { return client.disconnect(); });

}).catch(function(error) {

  console.log("Got error: " + error.message);

});

1.6. Configuring logging

Hot Rod JS clients use log4js, which you can modify by providing configuration in JSON format.

Procedure
  1. Create a logging configuration in JSON format.

    For example, the following JSON configures an appender that writes TRACE level log events to file:

    {
      "appenders": {
        "test": {
          "type": "fileSync",
          "filename": "my-log-file.log"
        }
      },
      "categories": {
        "default": {
          "appenders": ["test"],
          "level": "trace"
        }
      }
    }
  2. Add the var log4js = require('log4js') statement to the Hot Rod JS client configuration.

  3. Specify the path to your JSON logging configuration with the log4js.configure() method, as in the following example:

    var log4js = require('log4js');
    log4js.configure('path/to/my-log4js.json');
Additional resources

2. Using Hot Rod JS clients

Take a look at some examples for using the Hot Rod JS client with Infinispan.

2.1. Hot Rod JS client examples

After you install and configure your Hot Rod JS client, start using it by trying out some basic cache operations before moving on to more complex interactions with Infinispan.

2.1.1. Hello world

Create a cache named "myCache" on Infinispan Server then add and retrieve an entry.

var infinispan = require('infinispan');

// Connect to Infinispan Server.
// Use an existing cache named "myCache".
var connected = infinispan.client(
  {port: 11222, host: '127.0.0.1'},
  {
    cacheName: 'myCache',
    topologyUpdates: false,
    authentication: {
      enabled: true,
      saslMechanism: 'DIGEST-MD5',
      userName: 'username',
      password: 'changeme'
    }
  }
);

connected.then(function (client) {

  console.log('Connected to `myCache`');

  // Add an entry to the cache.
  var clientPut = client.put('hello', 'world');

  // Retrieve the entry you added.
  var clientGet = clientPut.then(
      function() { return client.get('hello'); });

  // Print the value of the entry.
  var showGet = clientGet.then(
      function(value) { console.log('get(hello)=' + value); });

  // Disconnect from Infinispan Server.
  return client.disconnect();

}).catch(function(error) {

  // Log any errors.
  console.log("Got error: " + error.message);

});

2.1.2. Working with entries and retrieving cache statistics

Add, retrieve, remove single entries and view statistics for the cache.

var infinispan = require('infinispan');

var connected = infinispan.client(
  {port: 11222, host: '127.0.0.1'},
  {
    cacheName: 'myCache',
    authentication: {
        enabled: true,
        saslMechanism: 'DIGEST-MD5',
        userName: 'username',
        password: 'changeme'
    }
  }
);

connected.then(function (client) {

  var clientPut = client.put('key', 'value');

  var clientGet = clientPut.then(
      function() { return client.get('key'); });

  var showGet = clientGet.then(
      function(value) { console.log('get(key)=' + value); });

  var clientRemove = showGet.then(
      function() { return client.remove('key'); });

  var showRemove = clientRemove.then(
      function(success) { console.log('remove(key)=' + success); });

  var clientStats = showRemove.then(
    function() { return client.stats(); });

  var showStats = clientStats.then(
    function(stats) {
      console.log('Number of stores: ' + stats.stores);
      console.log('Number of cache hits: ' + stats.hits);
      console.log('All statistics: ' + JSON.stringify(stats, null, " "));
    });

  return showStats.finally(
      function() { return client.disconnect(); });

}).catch(function(error) {

  console.log("Got error: " + error.message);

});

2.1.3. Working with multiple cache entries

Create multiple cache entries with simple recursive loops.

var infinispan = require('infinispan');

var connected = infinispan.client(
  {port: 11222, host: '127.0.0.1'},
  {
    cacheName: 'myCache',
    authentication: {
        enabled: true,
        saslMechanism: 'DIGEST-MD5',
        userName: 'username',
        password: 'changeme'
    }
  }
);

connected.then(function (client) {
  var data = [
    {key: 'multi1', value: 'v1'},
    {key: 'multi2', value: 'v2'},
    {key: 'multi3', value: 'v3'}];

  var clientPutAll = client.putAll(data);

  var clientGetAll = clientPutAll.then(
    function() { return client.getAll(['multi2', 'multi3']); });

  var showGetAll = clientGetAll.then(
    function(entries) {
      console.log('getAll(multi2, multi3)=%s', JSON.stringify(entries));
    }
  );

  var clientIterator = showGetAll.then(
    function() { return client.iterator(1); });

  var showIterated = clientIterator.then(
    function(it) {
      function loop(promise, fn) {
        // Simple recursive loop over the iterator's next() call.
        return promise.then(fn).then(function (entry) {
          return entry.done
            ? it.close().then(function () { return entry.value; })
            : loop(it.next(), fn);
        });
      }

      return loop(it.next(), function (entry) {
        console.log('iterator.next()=' + JSON.stringify(entry));
        return entry;
      });
    }
  );

  var clientClear = showIterated.then(
    function() { return client.clear(); });

  return clientClear.finally(
    function() { return client.disconnect(); });

}).catch(function(error) {

  console.log("Got error: " + error.message);

});

2.1.4. Using Async and Await constructs

Node.js provides async and await constructs that can simplify cache operations.

Single cache entries
const infinispan = require("infinispan");

const log4js = require('log4js');
log4js.configure('example-log4js.json');

async function test() {
  await new Promise((resolve, reject) => setTimeout(() => resolve(), 1000));
  console.log('Hello, World!');

  let client = await infinispan.client({port: 11222, host: '127.0.0.1'});
  console.log(`Connected to Infinispan dashboard data`);

  await client.put('key', 'value');

  let value = await client.get('key');
  console.log('get(key)=' + value);

  let success = await client.remove('key');
  console.log('remove(key)=' + success);

  let stats = await client.stats();
  console.log('Number of stores: ' + stats.stores);
  console.log('Number of cache hits: ' + stats.hits);
  console.log('All statistics: ' + JSON.stringify(stats, null, " "));

  await client.disconnect();
}

test();
Multiple cache entries
const infinispan = require("infinispan");

const log4js = require('log4js');
log4js.configure('example-log4js.json');

async function test() {
  let client = await infinispan.client({port: 11222, host: '127.0.0.1'});
  console.log(`Connected to Infinispan dashboard data`);

  let data = [
    {key: 'multi1', value: 'v1'},
    {key: 'multi2', value: 'v2'},
    {key: 'multi3', value: 'v3'}];

  await client.putAll(data);

  let entries = await client.getAll(['multi2', 'multi3']);
  console.log('getAll(multi2, multi3)=%s', JSON.stringify(entries));

  let iterator = await client.iterator(1);

  let entry = {done: true};

  do {
    entry = await iterator.next();
    console.log('iterator.next()=' + JSON.stringify(entry));
  } while (!entry.done);

  await iterator.close();

  await client.clear();

  await client.disconnect();
}

test();

2.1.5. Performing admin operations

Use the admin property on the client to manage caches and Protobuf schemas on Infinispan Server.

Admin operations let you create and remove caches, list cache names, update configuration attributes, manage index schemas, and register or remove Protobuf schemas.

Admin operations require the authenticated user to have administrative permissions on Infinispan Server.

var infinispan = require('infinispan');

var connected = infinispan.client(
  {port: 11222, host: '127.0.0.1'},
  {
    authentication: {
      enabled: true,
      saslMechanism: 'SCRAM-SHA-256',
      userName: 'admin',
      password: 'changeme'
    }
  }
);

connected.then(function (client) {

  // Create a cache with a JSON configuration.
  var cacheConfig = JSON.stringify({
    'local-cache': {
      encoding: { 'media-type': 'text/plain' }
    }
  });
  var create = client.admin.createCache('myNewCache', cacheConfig);

  // Get or create a cache (creates if it does not exist).
  var getOrCreate = create.then(function() {
    return client.admin.getOrCreateCache('myNewCache', cacheConfig);
  });

  // List all cache names.
  var listNames = getOrCreate.then(function() {
    return client.admin.cacheNames();
  });
  listNames.then(function(names) {
    console.log('Cache names: ' + JSON.stringify(names));
  });

  // Remove a cache.
  var remove = listNames.then(function() {
    return client.admin.removeCache('myNewCache');
  });

  // Register a Protobuf schema.
  var registerSchema = remove.then(function() {
    return client.admin.registerSchema('person.proto',
      'package example;\n' +
      'message Person {\n' +
      '  required string name = 1;\n' +
      '}\n');
  });

  // Remove a Protobuf schema.
  var removeSchema = registerSchema.then(function() {
    return client.admin.removeSchema('person.proto');
  });

  // Disconnect from Infinispan Server.
  return removeSchema.then(function() {
    return client.disconnect();
  });

}).catch(function(error) {

  // Log any errors.
  console.log("Got error: " + error.message);

});
Table 1. Available admin operations
Method Description

createCache(name, config, opts)

Creates a cache with a JSON, XML, or YAML configuration. Use opts.template to create from a server template. Use opts.flags (e.g. 'VOLATILE') for non-persistent caches.

getOrCreateCache(name, config, opts)

Creates a cache if it does not already exist, otherwise returns the existing cache.

removeCache(name)

Removes a cache and all its data.

cacheNames()

Returns a list of all cache names on the server.

updateConfigurationAttribute(name, attribute, value)

Updates a mutable configuration attribute on a cache (e.g. 'memory.max-count').

reindex(name)

Rebuilds indexes for an indexed cache.

updateIndexSchema(name)

Updates the index schema for an indexed cache.

registerSchema(name, schema)

Registers or updates a Protobuf schema (.proto file) on the server.

removeSchema(name)

Removes a registered Protobuf schema from the server.

2.1.6. Running server-side scripts

You can add custom scripts to Infinispan Server and then run them from Hot Rod JS clients.

Sample script
// mode=local,language=javascript,parameters=[k, v],datatype='text/plain; charset=utf-8'
cache.put(k, v);
cache.get(k);
Script execution
var infinispan = require('infinispan');
var readFile = Promise.denodeify(require('fs').readFile);

var connected = infinispan.client(
  {port: 11222, host: '127.0.0.1'}
  {
    // Configure client connections with authentication and encryption here.
  }
);

connected.then(function (client) {

  var addScriptFile = readFile('sample-script.js').then(
    function(file) {
      return client.addScript('sample-script', file.toString());
    });

  var clientExecute = addScriptFile.then(
    function() {
      return client.execute('sample-script', {k: 'exec-key', v: 'exec-value'});
    });

  var showExecute = clientExecute.then(
    function(ret) { console.log('Script execution returned: ' + ret); });

  return showExecute.finally(
    function() { return client.disconnect(); });

}).catch(function(error) {

  console.log("Got error: " + error.message);

});

2.1.7. Registering event listeners

Event listeners notify Hot Rod JS clients when cache updates occur, including when entries are created, modified, removed, or expired.

Events for entry creation and modification notify clients about keys and values. Events for entry removal and expiration notify clients about keys only.

Event listener registration
var infinispan = require('infinispan');

var connected = infinispan.client(
  {port: 11222, host: '127.0.0.1'},
  {
    // Configure client connections with authentication and encryption here.
  }
);

connected.then(function (client) {

    var clientAddListenerCreate = client.addListener('create', onCreate);

    var clientAddListeners = clientAddListenerCreate.then(
        function(listenerId) {
            // Associate multiple callbacks with a single client-side listener.
            // To do this, register listeners with the same listener ID.
            var clientAddListenerModify =
                client.addListener('modify', onModify, {listenerId: listenerId});

            var clientAddListenerRemove =
                client.addListener('remove', onRemove, {listenerId: listenerId});

            return Promise.all([clientAddListenerModify, clientAddListenerRemove]);
        });

    var clientCreate = clientAddListeners.then(
        function() { return client.putIfAbsent('eventful', 'v0'); });

    var clientModify = clientCreate.then(
        function() { return client.replace('eventful', 'v1'); });

    var clientRemove = clientModify.then(
        function() { return client.remove('eventful'); });

    var clientRemoveListener =
        Promise.all([clientAddListenerCreate, clientRemove]).then(
            function(values) {
                var listenerId = values[0];
                return client.removeListener(listenerId);
            });

    return clientRemoveListener.finally(
        function() { return client.disconnect(); });

}).catch(function(error) {

    console.log("Got error: " + error.message);

});

function onCreate(key, version) {
    console.log('[Event] Created key: ' + key +
        ' with version: ' + JSON.stringify(version));
}

function onModify(key, version) {
    console.log('[Event] Modified key: ' + key +
        ', version after update: ' + JSON.stringify(version));
}

function onRemove(key) {
    console.log('[Event] Removed key: ' + key);
}

You can tune notifications from event listeners to avoid unnecessary roundtrips with the key-value-with-previous-converter-factory converter. This allows you to, for example, find out values associated with keys within the event instead of retrieving them afterwards.

Remote event converter
var infinispan = require('infinispan');

var connected = infinispan.client(
    {port: 11222, host: '127.0.0.1'}
    , {
        dataFormat : {
            keyType: 'application/json',
            valueType: 'application/json'
        }
    }
);

connected.then(function (client) {
    // Include the remote event converter to avoid unnecessary roundtrips.
    var opts = {
        converterFactory : {
            name: "key-value-with-previous-converter-factory"
        }
    };

    var clientAddListenerCreate = client.addListener('create', logEvent("Created"), opts);

    var clientAddListeners = clientAddListenerCreate.then(
        function(listenerId) {
            // Associate multiple callbacks with a single client-side listener.
            // To do this, register listeners with the same listener ID.
            var clientAddListenerModify =
                client.addListener('modify', logEvent("Modified"), {opts, listenerId: listenerId});

            var clientAddListenerRemove =
                client.addListener('remove', logEvent("Removed"), {opts, listenerId: listenerId});

            return Promise.all([clientAddListenerModify, clientAddListenerRemove]);
        });

    var clientCreate = clientAddListeners.then(
        function() { return client.putIfAbsent('converted', 'v0'); });

    var clientModify = clientCreate.then(
        function() { return client.replace('converted', 'v1'); });

    var clientRemove = clientModify.then(
        function() { return client.remove('converted'); });

    var clientRemoveListener =
        Promise.all([clientAddListenerCreate, clientRemove]).then(
            function(values) {
                var listenerId = values[0];
                return client.removeListener(listenerId);
            });

    return clientRemoveListener.finally(
        function() { return client.disconnect(); });

}).catch(function(error) {

    console.log("Got error: " + error.message);

});

function logEvent(prefix) {
    return function(event) {
        console.log(prefix + " key: " + event.key);
        console.log(prefix + " value: " + event.value);
        console.log(prefix + " previous value: " + event.prev);
    }
}

You can add custom converters to Infinispan Server. See the Infinispan documentation for information.

2.1.8. Using conditional operations

The Hot Rod protocol stores metadata about values in Infinispan. This metadata provides a deterministic factor that lets you perform cache operations for certain conditions. For example, if you want to replace the value of a key if the versions do not match.

Use the getWithMetadata method to retrieve metadata associated with the value for a key.

var infinispan = require('infinispan');

var connected = infinispan.client(
  {port: 11222, host: '127.0.0.1'}
  {
    // Configure client connections with authentication and encryption here.
  }
);

connected.then(function (client) {

  var clientPut = client.putIfAbsent('cond', 'v0');

  var showPut = clientPut.then(
      function(success) { console.log(':putIfAbsent(cond)=' + success); });

  var clientReplace = showPut.then(
      function() { return client.replace('cond', 'v1'); } );

  var showReplace = clientReplace.then(
      function(success) { console.log('replace(cond)=' + success); });

  var clientGetMetaForReplace = showReplace.then(
      function() { return client.getWithMetadata('cond'); });

  // Call the getWithMetadata method to retrieve the value and its metadata.
  var clientReplaceWithVersion = clientGetMetaForReplace.then(
      function(entry) {
        console.log('getWithMetadata(cond)=' + JSON.stringify(entry));
        return client.replaceWithVersion('cond', 'v2', entry.version);
      }
  );

  var showReplaceWithVersion = clientReplaceWithVersion.then(
      function(success) { console.log('replaceWithVersion(cond)=' + success); });

  var clientGetMetaForRemove = showReplaceWithVersion.then(
      function() { return client.getWithMetadata('cond'); });

  var clientRemoveWithVersion = clientGetMetaForRemove.then(
      function(entry) {
        console.log('getWithMetadata(cond)=' + JSON.stringify(entry));
        return client.removeWithVersion('cond', entry.version);
      }
  );

  var showRemoveWithVersion = clientRemoveWithVersion.then(
      function(success) { console.log('removeWithVersion(cond)=' + success)});

  return showRemoveWithVersion.finally(
      function() { return client.disconnect(); });

}).catch(function(error) {

  console.log("Got error: " + error.message);

});

2.1.9. Working with ephemeral data

Use the getWithMetadata and size methods expire cache entries.

var infinispan = require('infinispan');

var connected = infinispan.client(
  {port: 11222, host: '127.0.0.1'}
  {
    // Configure client connections with authentication and encryption here.
  }
);

connected.then(function (client) {

  var clientPutExpiry = client.put('expiry', 'value', {lifespan: '1s'});

  var clientGetMetaAndSize = clientPutExpiry.then(
    function() {
      // Compute getWithMetadata and size in parallel.
      return Promise.all([client.getWithMetadata('expiry'), client.size()]);
    });

  var showGetMetaAndSize = clientGetMetaAndSize.then(
    function(values) {
      console.log('Before expiration:');
      console.log('getWithMetadata(expiry)=' + JSON.stringify(values[0]));
      console.log('size=' + values[1]);
    });

  var clientContainsAndSize = showGetMetaAndSize.then(
    function() {
      sleepFor(1100); // Sleep to force expiration.
      return Promise.all([client.containsKey('expiry'), client.size()]);
    });

  var showContainsAndSize = clientContainsAndSize.then(
    function(values) {
      console.log('After expiration:');
      console.log('containsKey(expiry)=' + values[0]);
      console.log('size=' + values[1]);
    });

  return showContainsAndSize.finally(
    function() { return client.disconnect(); });

}).catch(function(error) {

  console.log("Got error: " + error.message);

});

function sleepFor(sleepDuration){
  var now = new Date().getTime();
  while(new Date().getTime() < now + sleepDuration){ /* Do nothing. */ }
}

2.1.10. Using distributed counters

Distributed counters provide cluster-wide atomic counters that you can use to coordinate state across Infinispan Server nodes. Infinispan supports two types of counters:

  • Strong counters provide linearizable semantics with optional upper and lower bounds.

  • Weak counters are more performant but offer weaker consistency guarantees.

var infinispan = require('infinispan');

var connected = infinispan.client(
  {port: 11222, host: '127.0.0.1'},
  {
    authentication: {
      enabled: true,
      saslMechanism: 'SCRAM-SHA-256',
      userName: 'admin',
      password: 'changeme'
    }
  }
);

connected.then(function (client) {

  // Create a strong counter with an initial value of 0.
  var create = client.counterCreate('my-counter', {
    type: 'strong',
    storage: 'PERSISTENT',
    initialValue: 0
  });

  // Get the current counter value.
  var get = create.then(function() {
    return client.counterGet('my-counter');
  });
  get.then(function(value) {
    console.log('Counter value: ' + value); // 0
  });

  // Add a value and return the new counter value.
  var addAndGet = get.then(function() {
    return client.counterAddAndGet('my-counter', 5);
  });
  addAndGet.then(function(value) {
    console.log('After add: ' + value); // 5
  });

  // Set a value and return the previous counter value.
  var getAndSet = addAndGet.then(function() {
    return client.counterGetAndSet('my-counter', 10);
  });
  getAndSet.then(function(previous) {
    console.log('Previous value: ' + previous); // 5
  });

  // Compare and swap: update only if the current value matches.
  var cas = getAndSet.then(function() {
    return client.counterCompareAndSwap('my-counter', 10, 20);
  });
  cas.then(function(value) {
    console.log('After CAS: ' + value); // 20
  });

  // Reset the counter to its initial value.
  var reset = cas.then(function() {
    return client.counterReset('my-counter');
  });

  // Check if a counter is defined.
  var isDefined = reset.then(function() {
    return client.counterIsDefined('my-counter');
  });
  isDefined.then(function(defined) {
    console.log('Is defined: ' + defined); // true
  });

  // Retrieve the counter configuration.
  var getConfig = isDefined.then(function() {
    return client.counterGetConfiguration('my-counter');
  });
  getConfig.then(function(config) {
    console.log('Counter type: ' + config.type); // strong
  });

  // Remove the counter.
  var remove = getConfig.then(function() {
    return client.counterRemove('my-counter');
  });

  // Disconnect from Infinispan Server.
  return remove.then(function() {
    return client.disconnect();
  });

}).catch(function(error) {

  // Log any errors.
  console.log("Got error: " + error.message);

});
Table 2. Available counter operations
Method Description

counterCreate(name, config)

Creates a counter. Config requires type ('strong' or 'weak'), and optionally storage, initialValue, upperBound, lowerBound (strong), or concurrencyLevel (weak).

counterGet(name)

Returns the current counter value, or undefined if the counter does not exist.

counterAddAndGet(name, value)

Adds a value to the counter and returns the new value.

counterGetAndSet(name, value)

Sets a value and returns the previous value.

counterCompareAndSwap(name, expect, update)

Updates the counter to update only if the current value equals expect. Returns the value before the operation.

counterReset(name)

Resets the counter to its initial value.

counterIsDefined(name)

Returns true if the counter exists.

counterGetConfiguration(name)

Returns the counter configuration, or undefined if the counter does not exist.

counterRemove(name)

Removes the counter from the cluster.

2.1.11. Working with queries

Use the query method to perform queries on your caches. You must configure Hot Rod JS client to have application/x-protostream data format for values in your caches.

const infinispan = require('infinispan');
const protobuf = require('protobufjs');
// This example uses async/await paradigma
(async function () {
  // User data protobuf definition
  const cacheValueProtoDef = `package awesomepackage;
  /**
   * @TypeId(1000044)
   */
  message AwesomeUser {
      required string name = 1;
      required int64 age = 2;
      required bool isVerified =3;
  }`
  try {
    // Creating clients for two caches:
    // - ___protobuf_metadata for registering .proto file
    // - queryCache for user data
    const connectProp = { port: 11222, host: '127.0.0.1' };
    const commonOpts = {
      version: '3.0',
      authentication: {
        enabled: true,
        saslMechanism: 'DIGEST-MD5',
        userName: 'admin',
        password: 'pass'
      }
    };
    const protoMetaClientOps = {
      cacheName: '___protobuf_metadata',
      dataFormat: { keyType: "text/plain", valueType: "text/plain" }
    }
    const clientOps = {
      dataFormat: { keyType: "text/plain", valueType: "application/x-protostream" },
      cacheName: 'queryCache'
    }
    var protoMetaClient = await infinispan.client(connectProp, Object.assign(commonOpts, protoMetaClientOps));
    var client = await infinispan.client(connectProp, Object.assign(commonOpts, clientOps));

    // Registering protobuf definition on server
    await protoMetaClient.put("awesomepackage/AwesomeUser.proto", cacheValueProtoDef);

    // Registering protobuf definition on protobufjs
    const root = protobuf.parse(cacheValueProtoDef).root;
    const AwesomeUser = root.lookupType(".awesomepackage.AwesomeUser");
    client.registerProtostreamRoot(root);
    client.registerProtostreamType(".awesomepackage.AwesomeUser", 1000044);

    // Cleanup and populating the cache
    await client.clear();
    for (let i = 0; i < 10; i++) {
      const payload = { name: "AwesomeName" + i, age: i, isVerified: (Math.random() < 0.5) };
      const message = AwesomeUser.create(payload);
      console.log("Creating entry:", message);
      await client.put(i.toString(), message)
    }
    // Run the query
    const queryStr = `select u.name,u.age from awesomepackage.AwesomeUser u where u.age<20 order by u.name asc`;
    console.log("Running query:", queryStr);
    const query = await client.query({ queryString: queryStr });
    console.log("Query result:");
    console.log(query);
  } catch (err) {
    handleError(err);
  } finally {
    if (client) {
      await client.disconnect();
    }
    if (protoMetaClient) {
      await protoMetaClient.disconnect();
    }
  }
})();

function handleError(err) {
  if (err.message.includes("'queryCache' not found")) {
    console.log('*** ERROR ***');
    console.log(`*** This example needs a cache 'queryCache' with the following config:
    {
      "local-cache": {
        "statistics": true,
        "encoding": {
        "key": {
          "media-type": "text/plain"
        },
        "value": {
          "media-type": "application/x-protostream"
}}}}`)
  } else {
    console.log(err);
  }
}

See Querying Infinispan caches for more information.

2.1.12. Using transactions

Transactions let you group multiple cache operations into an atomic unit. The Hot Rod JS client buffers operations locally and sends them to the server on commit.

For detailed information, see Transactions.

2.2. Transactions

Transactions let you group multiple cache operations into an atomic unit that is either committed or rolled back together. The Hot Rod JS client implements client-side transactions using the Hot Rod PREPARE_TX, COMMIT_TX, and ROLLBACK_TX protocol operations.

2.2.1. Transaction requirements

Transactional caches on Infinispan Server must use NON_XA transaction mode with PESSIMISTIC locking:

<distributed-cache>
  <encoding>
    <key media-type="text/plain"/>
    <value media-type="text/plain"/>
  </encoding>
  <transaction mode="NON_XA" locking="PESSIMISTIC"/>
</distributed-cache>

2.2.2. How transactions work

When you begin a transaction, the client buffers all put() and remove() operations locally. Calls to get() check the local buffer first, then fall back to the server.

When you read a key from the server during a transaction, the client records the entry version. On commit, the server verifies that versions have not changed, providing conflict detection for read-then-write patterns.

On commit, the client sends all buffered modifications to the server in a single PREPARE_TX request. The Hot Rod JS client uses one-phase commit for single-cache transactions, so no separate COMMIT_TX request is needed.

On rollback, the client discards all buffered modifications and sends a ROLLBACK_TX request to the server.

2.2.3. Using transactions

Procedure
  1. Get the transaction manager from the client.

  2. Call begin() to start a transaction.

  3. Perform cache operations (put(), get(), remove()).

  4. Call commit() to apply changes atomically, or rollback() to discard them.

var infinispan = require('infinispan');

var connected = infinispan.client(
  {port: 11222, host: '127.0.0.1'},
  {
    authentication: {
      enabled: true,
      saslMechanism: 'SCRAM-SHA-256',
      userName: 'admin',
      password: 'changeme'
    },
    clientIntelligence: 'BASIC'
  }
);

connected.then(function (adminClient) {

  // Create a cache with transactional configuration.
  var txConfig =
    '<distributed-cache>' +
    '<encoding><key media-type="text/plain"/><value media-type="text/plain"/></encoding>' +
    '<transaction mode="NON_XA" locking="PESSIMISTIC"/>' +
    '</distributed-cache>';

  var create = adminClient.admin.getOrCreateCache('txCache', txConfig);

  return create.then(function() {
    return adminClient.disconnect();
  }).then(function() {

    // Connect to the transactional cache.
    return infinispan.client(
      {port: 11222, host: '127.0.0.1'},
      {
        authentication: {
          enabled: true,
          saslMechanism: 'SCRAM-SHA-256',
          userName: 'admin',
          password: 'changeme'
        },
        cacheName: 'txCache',
        topologyUpdates: false,
        dataFormat: {
          keyType: 'text/plain',
          valueType: 'text/plain'
        }
      }
    );

  }).then(function(client) {

    // Get the transaction manager.
    var tm = client.getTransactionManager();

    // Begin a transaction, put entries, and commit.
    var committed = tm.begin().then(function() {
      return client.put('key1', 'value1');
    }).then(function() {
      return client.put('key2', 'value2');
    }).then(function() {
      return tm.commit();
    });

    // Begin a transaction, put an entry, and roll back.
    var rolledBack = committed.then(function() {
      return tm.begin();
    }).then(function() {
      return client.put('key3', 'should-not-exist');
    }).then(function() {
      return tm.rollback();
    });

    // Read-then-write with version-based conflict detection.
    var readWrite = rolledBack.then(function() {
      return client.put('counter', '0');
    }).then(function() {
      return tm.begin();
    }).then(function() {
      return client.get('counter');
    }).then(function(current) {
      return client.put('counter', String(parseInt(current) + 1));
    }).then(function() {
      return tm.commit();
    });

    // Remove an entry within a transaction.
    var removed = readWrite.then(function() {
      return client.put('temp', 'ephemeral');
    }).then(function() {
      return tm.begin();
    }).then(function() {
      return client.remove('temp');
    }).then(function() {
      return tm.commit();
    });

    // Disconnect from Infinispan Server.
    return removed.then(function() {
      return client.disconnect();
    });

  });

}).catch(function(error) {

  // Log any errors.
  console.log("Got error: " + error.message);

});
Table 3. Available transaction operations
Method Description

getTransactionManager()

Returns the transaction manager for the client.

begin()

Starts a new transaction. All subsequent put(), get(), and remove() calls are associated with this transaction.

commit()

Commits the transaction. Sends all buffered modifications to the server atomically. Throws an error if the server rejects the transaction due to a version conflict.

rollback()

Rolls back the transaction. Discards all buffered modifications.

isActive()

Returns true if a transaction is currently in progress.

Connect to the transactional cache with topologyUpdates: false to prevent key-based routing, which is not compatible with transaction operations.