Valkey

The Redis store on node-redis, for Valkey and Redis servers that need TLS settings, rotating credentials, or reconnect control.

server/orders.ts
import { createServer } from 'experimental-a2/server'
import { valkey } from 'experimental-a2/store-valkey'
import { orders } from '@/contracts'

export const ordersServer = createServer({
  contract: orders,
  store: valkey({ url: process.env.VALKEY_URL }),
  consumers: [
    /* ... */
  ],
})
npm install redis

Stores sessions exactly like Redis, on node-redis connections instead of ioredis. Use it with Valkey or Redis when a URL isn't enough: managed servers such as ElastiCache that authenticate with short-lived tokens, custom TLS settings, or your own reconnect and timeout policy. It needs a single server or a non-cluster replication group: it holds one non-cluster connection for commands and one for pub/sub.

Options

Option
urla redis:// or rediss:// URL
clientOptionsnode-redis createClient options for both connections, such as socket, credentialsProvider, commandOptions, disableOfflineQueue
keyPrefixisolates several apps on one server. Default 'a2'
clock, idstest seams for timestamps and generated event ids

Pass url, clientOptions, or both; url wins over clientOptions.url. A2 always connects with RESP: 2, so clientOptions has no RESP, nor the options that need RESP3 (clientSideCache, emitInvalidate, maintNotifications).

Rotating credentials

An async credentials provider runs each time a connection authenticates, including every reconnect, so each one gets a fresh token:

server/store.ts
import { valkey } from 'experimental-a2/store-valkey'

export const store = valkey({
  clientOptions: {
    socket: { host: 'my-cache.example.com', port: 6379, tls: true },
    credentialsProvider: {
      type: 'async-credentials-provider',
      credentials: async () => {
        // your token source, such as an ElastiCache IAM auth token:
        // const password = await createAuthToken()
        const password = 'token'
        return { username: 'a2', password }
      },
    },
  },
})

Connections

A2 creates two clients from your options: one for commands, on the first store call, and one subscriber, on the first live stream or presence subscription. Every live stream and presence subscription in the process shares that subscriber. close() destroys both.

  • A new client is used once it first connects: calls wait for it, each for at most commandOptions.timeout (five seconds without one), so a cold start works with disableOfflineQueue.
  • While a client reconnects, node-redis's own options decide what happens to commands: they wait in its offline queue, bounded by commandOptions.timeout, or fail at once with disableOfflineQueue. A2 applies the same rules to a new live stream's subscription, which node-redis would otherwise queue without a bound.
  • A reconnect follows your socket.reconnectStrategy. When the subscriber reconnects, every live stream reads what it missed right away, without waiting for the safety re-read.
  • When a client's reconnect strategy gives up, A2 replaces it: the command client on the next store call, the subscriber on the next new live stream or presence subscription. Until then, live streams still deliver within the ten-second safety re-read.
  • A2 listens for each client's 'error' events, so a dropped socket never crashes the process. Failures surface as STORE_UNAVAILABLE from the call that hit them.

The upgrade rules, key layout, and per-command costs are the same as Redis.