Valkey
The Redis store on node-redis, for Valkey and Redis servers that need TLS settings, rotating credentials, or reconnect control.
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 | |
|---|---|
url | a redis:// or rediss:// URL |
clientOptions | node-redis createClient options for both connections, such as socket, credentialsProvider, commandOptions, disableOfflineQueue |
keyPrefix | isolates several apps on one server. Default 'a2' |
clock, ids | test 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:
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 withdisableOfflineQueue. - 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 withdisableOfflineQueue. 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 asSTORE_UNAVAILABLEfrom the call that hit them.
The upgrade rules, key layout, and per-command costs are the same as Redis.