iovalkey

repository·main·Indexed 42 days ago

https://github.com/valkey-io/iovalkey

A robust, performance-focused, and full-featured Node.js client for Valkey and Redis, designed as a fork of ioredis. Version 0.4.0 supports advanced features including Cluster, Sentinel, Streams, Lua scripting, and pipelining. It provides support for binary data, transparent key prefixing, and specialized scan streams for Hashes, Zsets, and Sets.

Tokens
36.6K
Snippets
137
Records
179
Agent score
53%

What's inside iovalkey

  1. How to use preferredSlaves for slave selection

    main

    If you set role: 'slave', you can use preferredSlaves to control which slave is selected from the pool. You can provide either an array of priorities or a selection function.

    Option 1: Priority Array

    An array of slave objects ordered by priority (lowest prio value first). The default prio is 1.

    Option 2: Selection Function

    A function that receives an array of availableSlaves and returns a single slave object or false to fall back to a random slave.

    Available Slave Object Format: { ip: string, port: string, flags: string }

    // available slaves format
    const availableSlaves = [{ ip: "127.0.0.1", port: "31231", flags: "slave" }];
    
    // preferredSlaves array format
    let preferredSlaves = [
      { ip: "127.0.0.1", port: "31231", prio: 1 },
      { ip: "127.0.0.1", port: "31232", prio: 2 },
    ];
    
    // preferredSlaves function format
    preferredSlaves = function (availableSlaves) {
      for (let i = 0; i < availableSlaves.length; i++) {
        const slave = availableSlaves[i];
        if (slave.ip === "127.0.0.1") {
          if (slave.port === "31234") {
            return slave;
          }
        }
      }
      // if no preferred slaves are available a random one is used
      return false;
    };
    
    const valkey = new Valkey({
      sentinels: [
        { host: "127.0.0.1", port: 26379 },
        { host: "127.0.0.1", port: 26380 },
      ],
      name: "mymaster",
      role: "slave",
      preferredSlaves: preferredSlaves,
    });
  2. Transactions and Pipelining in Cluster Mode

    main

    While Valkey.Cluster supports transactions and pipelines, observe these constraints:

    1. Key Locality: All keys in a pipeline must belong to slots served by the same node, as iovalkey sends the entire pipeline to a single node.
    2. Multi Constraint: You cannot use multi without a pipeline (i.e., use cluster.multi({ pipeline: true })). Without a pipeline, the client cannot determine which node should receive the multi command.
    3. Automatic Resending: If a pipeline receives MOVED or ASK errors, iovalkey will automatically resend the whole pipeline to the correct node ONLY if:
      • All errors in the pipeline are identical (e.g., all point to the same new node).
      • All successfully executed commands are read-only (to prevent side effects during resending).
  3. Manage Transactions with multi()

    main

    Transactions in iovalkey are handled via the multi() method. By default, multi() creates a Pipeline instance, meaning commands are queued and sent together upon calling .exec().

    Key Behaviors:

    • Atomicity/Errors: If a syntax error occurs in the command chain (e.g., wrong number of arguments), the entire transaction is discarded, and .exec() returns an error containing the previousErrors.
    • Callback Difference: Unlike pipeline(), when providing a callback to a chained command within multi(), the callback receives 'QUEUED' as the result instead of the command's actual reply.
    • Disabling Pipelining: To use transactions without the automatic pipelining behavior (sending commands immediately), pass { pipeline: false } to multi().
    • Batch Initialization: You can pass an array of commands directly to multi().
    • Inline Transactions: You can nest multi() calls within a pipeline() to group subsets of commands into transactions.
    // Standard transaction
    valkey
      .multi()
      .set("foo", "bar")
      .get("foo")
      .exec((err, results) => {
        // results === [[null, 'OK'], [null, 'bar']]
      });
    
    // Transaction without pipelining
    valkey.multi({ pipeline: false });
    valkey.set("foo", "bar");
    valkey.get("foo");
    valkey.exec((err, result) => {
      // result === [[null, 'OK'], [null, 'bar']]
    });
  4. Implement Pub/Sub with iovalkey

    main

    iovalkey uses the Node.js events module to handle Pub/Sub.

    Important: A single Valkey instance cannot act as both a publisher and a subscriber simultaneously. When subscribe() or psubscribe() is called, the connection enters 'subscriber mode', where only subscription-related commands (e.g., subscribe, unsubscribe, ping, quit) are valid. To publish messages while listening, you must create two separate Valkey instances.

    const Valkey = require("iovalkey");
    const sub = new Valkey();
    const pub = new Valkey();
    
    // Subscriber setup
    sub.subscribe("my-channel", (err, count) => {
      console.log(`Subscribed to ${count} channels`);
    });
    
    sub.on("message", (channel, message) => {
      console.log(`Received ${message} from ${channel}`);
    });
    
    // Subscriber for binary data
    sub.on("messageBuffer", (channel, message) => {
      // channel and message are Buffers
    });
    
    // Pattern subscription
    sub.psubscribe("pat?ern", (err, count) => {});
    sub.on("pmessage", (pattern, channel, message) => {});
    
    // Publisher usage
    pub.publish("my-channel", "hello");
  5. Configure Read-Write Splitting in a Cluster

    main

    You can scale your cluster by directing read queries to slaves using the scaleReads option. The default is "master" (all queries go to masters).

    Available scaleReads values:

    • "all": Write queries to masters; read queries to masters or slaves randomly.
    • "slave": Write queries to masters; read queries to slaves.
    • function(nodes, command): A custom selector. nodes is an array where the first element is the master for the relevant slots. If the function returns an array, a random node from that list is selected.

    Note: When using "slave", results may reflect replication lag.

    const cluster = new Valkey.Cluster(
      [/* nodes */],
      {
        scaleReads: "slave",
      }
    );
    cluster.set("foo", "bar"); // Sent to a master
    cluster.get("foo", (err, res) => {
      // Sent to a slave
    });
    const cluster = new Valkey.Cluster(
      [
        /* nodes */
      ],
      {
        scaleReads: "slave",
      }
    );
    cluster.set("foo", "bar"); // This query will be sent to one of the masters.
    cluster.get("foo", (err, res) => {
      // This query will be sent to one of the slaves.
    });
  6. Basic Usage of iovalkey

    main

    You can use iovalkey with either Node.js callbacks or Native Promises. If the last argument to a method is not a function, it returns a Promise. All arguments are passed directly to the Valkey server, meaning all Valkey commands are supported via method names in lowercase.

    const Valkey = require("iovalkey");
    const valkey = new Valkey();
    
    // Using Promises
    await valkey.set("mykey", "value");
    const result = await valkey.get("mykey");
    
    // Using Callbacks
    valkey.get("mykey", (err, result) => {
      if (err) console.error(err);
      else console.log(result);
    });
    
    // Direct command mapping (equivalent to CLI: SET mykey hello EX 10)
    valkey.set("mykey", "hello", "EX", 10);
  7. Handle Binary Data

    main

    iovalkey supports binary data out of the box. You can send Buffer objects directly using standard commands like set. To retrieve data as a Buffer instead of a UTF-8 string, use the command variant with the Buffer suffix (e.g., getBuffer).

    // Sending binary data
    valkey.set("foo", Buffer.from([0x62, 0x75, 0x66]));
    
    // Receiving binary data
    const result = await valkey.getBuffer("foo");
    // result is <Buffer 62 75 66>
    
    // Using Buffer suffix to get old value during SET
    const oldVal = await valkey.setBuffer("foo", "new value", "GET");
  8. Pause and resume scan streams for async tasks

    main

    When performing asynchronous operations (like migrating data to another database) inside a data event handler, you should use stream.pause() and stream.resume() to prevent the stream from overwhelming the client or the destination. This ensures the scanning process waits for your async tasks to complete before fetching the next batch of keys.

    const stream = valkey.scanStream();
    stream.on("data", (resultKeys) => {
      // Pause the stream from scanning more keys until we've migrated the current keys.
      stream.pause();
    
      Promise.all(resultKeys.map(migrateKeyToMySQL)).then(() => {
        // Resume the stream here.
        stream.resume();
      });
    });
    
    stream.on("end", () => {
      console.log("done migration");
    });
  9. Connect to AWS ElastiCache Clusters with TLS

    main

    AWS ElastiCache for Valkey (Clustered Mode) requires TLS. To avoid invalid certificate errors, you must provide a dnsLookup function that returns the address as-is and enable tls in redisOptions.

    const cluster = new Valkey.Cluster(
      [
        {
          host: "clustercfg.myCluster.abcdefg.xyz.cache.amazonaws.com",
          port: 6379,
        },
      ],
      {
        dnsLookup: (address, callback) => callback(null, address),
        redisOptions: {
          tls: {},
        },
      }
    );
  10. Connect to Valkey

    main

    A Valkey instance creates a connection upon instantiation. You can connect using a port number, a host/port combination, a Unix socket, a configuration object, or a connection URI (supporting valkey://, valkeys:// for TLS, and legacy redis:///rediss:// schemes).

    // Default: 127.0.0.1:6379
    new Valkey();
    
    // By port
    new Valkey(6380);
    
    // By host and port
    new Valkey(6379, "192.168.1.1");
    
    // By Unix socket
    new Valkey("/tmp/valkey.sock");
    
    // By configuration object
    new Valkey({
      port: 6379,
      host: "127.0.0.1",
      username: "default",
      password: "my-top-secret",
      db: 0
    });
    
    // By URI
    new Valkey("valkey://username:authpassword@127.0.0.1:6380/4");
  11. Install iovalkey

    main

    Install the iovalkey package using npm. If you are working in a TypeScript project, you should also install the Node.js type definitions.

    npm install iovalkey
    
    # For TypeScript projects
    npm install --save-dev @types/node
  12. Consume Valkey Streams

    main

    You can use xread to consume messages from a stream. When using the block argument, the client will wait for new data. To continuously listen, pass the ID of the last received message back into the next xread call.

    const Valkey = require("iovalkey");
    const valkey = new Valkey();
    
    async function listenForMessage(lastID = "$") {
      // results is an array of [key, messages]
      const results = await valkey.xread("block", 0, "STREAMS", "mystream", lastID);
      const [key, messages] = results[0];
    
      messages.forEach(msg => {
        console.log("ID: %s. Data: %O", msg[0], msg[1]);
      });
    
      // Use the last ID from the results for the next iteration
      await listenForMessage(messages[messages.length - 1][0]);
    }
    
    listenForMessage();