Creates a new Redis local storage provider and establishes a connection.
The constructor sets up the ioredis client with automatic reconnection,
error handling, and optional logging. The client connects lazily on the
first command, so construction itself does not block.
Redis connection and behavior configuration.
At minimum, provide either url or options.
If neither is provided, connects to localhost:6379.
// Connect to local Redis
const provider = new RedisLocalStorageProvider({});
// Connect to Azure Managed Redis with TLS
const provider = new RedisLocalStorageProvider({
url: 'rediss://default:ACCESS_KEY@myredis.redis.cache.windows.net:6380',
defaultTTLSeconds: 600
});
// Connect to AWS ElastiCache
const provider = new RedisLocalStorageProvider({
options: {
host: 'my-cluster.abc123.use1.cache.amazonaws.com',
port: 6379,
tls: {}
}
});
ReadonlySharedtrue — every process configured with the same URL and key prefix shares this keyspace.
See ILocalStorageProvider.SharedAcrossProcesses.
ReadonlySharesfalse — values are JSON-serialized onto the wire and parsed back on read, so a
Redis-backed cache never hands out a live reference to a caller's object.
See ILocalStorageProvider.SharesReferences.
ReadonlySupportstrue — that is the whole point of this provider: every server in the fleet reads what any
other wrote. See ILocalStorageProvider.SupportsCrossProcessPersistence.
Returns the underlying ioredis client instance for advanced operations.
Use with caution — direct client access bypasses key prefixing and
category tracking. Prefer the ILocalStorageProvider methods for
standard operations.
For pub/sub, use PublishMessage, PublishMessageAndWait and SubscribeToChannel instead: they share the provider's subscriber connection and namespace channels with the key prefix.
Whether the Redis client currently has an active connection.
Note: ioredis automatically reconnects on failure, so a false value
here is usually transient. The provider continues to accept commands
(they queue until reconnection succeeds or retry limit is hit).
Whether the provider was created with enablePubSub. When false, the channel methods do nothing.
Whether the pub/sub subscriber connection is currently active.
The highest fleet-wide epoch this process has observed. Exposed for diagnostics and for tests that need to assert what reconciliation decided; consumers should not need it.
Clears all keys belonging to a specific category, together with the category's index-group sets and the category set older versions of this provider maintained.
Keys are found with SCAN, so the operation also removes keys a peer wrote and keys whose
index entry was lost.
The category to clear. If empty, clears the "default" category.
ClearCategory, but reporting what happened instead of swallowing it. An administrator
running mj cache clear needs to know a category did NOT go: the old path logged (with
logging off, by default, in the CLI) and returned normally, so the command printed a key
count and "servers will reload" for a clear that never happened.
While Redis is unreachable the clear is recorded for the reconnect flush and reported as not done — the flush will clear it, but reporting success now would be exactly the lie this method exists to stop.
Ok, plus the error when it failed.
Gracefully disconnects from Redis.
Sends a QUIT command and waits for pending replies. After calling this
method, the provider should not be used for further operations.
Call this during application shutdown to ensure clean disconnection.
Checks if a key exists in the specified category.
This is more efficient than GetItem() when you only need to check
existence without retrieving the value (avoids transferring the value
over the network).
The key to check
Optionalcategory: stringOptional category for key isolation (defaults to "default")
true if the key exists and has not expired
Returns all live keys belonging to a specific category.
Walks the keyspace with SCAN, so the cost grows with the whole keyspace. Meant for
administration and diagnostics; per-entity lookups on the save path use
GetIndexGroupKeys.
The category to list keys from
Array of original key names (without the Redis prefix/category prefix)
Returns the keys written to category with IndexGroup = group that still exist.
Members whose key has expired or been removed are dropped from the group set as a side
effect, so the set shrinks back to the live keys whenever it is read. The read and the prune
are one Lua script: a peer's SADD cannot land between them and be pruned unseen.
The category the keys were written to
The index group (for the RunView cache, the entity name)
The live keys, without the Redis prefix/category prefix
Retrieves a value from Redis by key and optional category.
Redis stores values as strings — this method JSON-deserializes the stored value
internally so callers see a typed object back. Returns null for missing keys,
Redis unavailability, or corrupt JSON.
Expected type of the stored value. Caller-controlled.
The key to look up
Optionalcategory: stringOptional category for key isolation (defaults to "default")
Batched read using Redis MGET — one command, one network round-trip,
N values returned. For N keys this is ~N× faster than individual GETs
which each pay full RTT.
Inputs are deduplicated (same key requested twice → one Redis fetch, one
map entry). Missing keys, corrupt JSON entries, and Redis errors all map
to null for the affected key — callers see them as cache misses.
Expected type of all stored values. Caller-controlled.
The keys to retrieve.
Optionalcategory: stringOptional category for key isolation.
Returns the remaining time-to-live (in seconds) for a key.
The key to check
Optionalcategory: stringOptional category for key isolation (defaults to "default")
TTL in seconds, -1 if no expiration is set, -2 if the key doesn't exist,
or null if Redis is unavailable
Registers a callback for cache change events from other servers.
The callback fires whenever another server instance modifies a cached entry
(via SetItem, Remove, or ClearCategory).
Events from this server instance (identified by MJGlobal.ProcessUUID) are automatically filtered out.
Function invoked with the CacheChangedEvent
A function that, when called, removes this callback registration
Registers a callback for the loss of the Redis connection.
Fires on a transition only — not on the first connect, and not again while a reconnect is being retried. IsConnected reports the current state but not that it changed, so a consumer wanting to degrade or report health needs this rather than a poll.
Use this in preference to reading the log: status-level logging is suppressed when
GetProductionStatus() is true, so a deployed process says nothing when its cache client
dies.
Invoked with a short human-readable reason for the loss
A function that removes this registration
Registers a callback for the Redis connection being restored after a loss. Not fired for the first connection.
Cache correctness after the gap is handled by the provider itself; see OnReconciliationRequired for the signal that local state had to be dropped.
Invoked when the connection comes back
A function that removes this registration
Registers a callback for the provider having concluded, after a connection gap, that local cache state must be dropped.
The standard categories are flushed by the provider itself via OnCacheChanged. Use this for state those categories do not describe — an in-process engine, a derived index, a memoized permission set — which would otherwise survive a reconnect while stale.
Does not fire when nothing was invalidated anywhere during the gap.
Invoked when a flush is required
A function that removes this registration
Publishes an arbitrary message on a named channel. Fire-and-forget: the promise is not awaited and a failure is logged rather than raised, so a caller on a hot path is never blocked or broken by the message bus. Does nothing when pub/sub is disabled.
Use PublishMessageAndWait when the caller needs to know whether the message went out.
The channel is namespaced with the provider's key prefix, so several applications can share one Redis without hearing each other.
Logical channel name (unprefixed).
Message body. Serialize before calling — this layer is shape-agnostic.
Publishes a message on a named channel and waits for Redis to accept it.
Use this instead of PublishMessage when the caller must report honestly whether the message went out — for example, telling a user whether a cancellation reached the server running their request.
While the connection is down the publish waits for it to come back rather than failing, so a caller that needs a deadline should race it against a timeout.
Logical channel name (unprefixed), namespaced as in PublishMessage.
Message body. Serialize before calling — this layer is shape-agnostic.
The number of subscribers that received the message, across every process sharing the Redis — including this one, if it subscribes to the same channel. Zero means the message was published but nobody was listening.
Ends a lease this provider claimed, if it still holds it (compare-and-delete).
Extends a lease this provider holds, without being able to extend anyone else's: the script only acts when the stored token is this provider's. A lease that has already expired and been taken by another process is NOT stolen back — the caller learns it lost its turn.
true when this process still holds the lease afterwards.
Stores a value in Redis under the given key and optional category.
Redis stores values as strings — this method JSON-serializes the value internally. Callers should pass plain data (objects/arrays/primitives). Class instances will lose their prototype on retrieval; functions, Maps, Sets, and Dates have JSON's usual limitations.
The key expires after the per-call TTL when one is given, otherwise after the configured
defaultTTLSeconds. If neither is set (or the value is 0), the key persists indefinitely.
When the options carry an IndexGroup, the key is also added to that group's Redis Set so
GetIndexGroupKeys can find it. The set is kept alive at least as long as its
longest-lived member. There is no category-wide set: one would grow for ever once keys
expire, because expiry never removes set members. Category-wide operations use SCAN.
Type of the value being stored. Caller-controlled.
The key to store under
The value to store (will be JSON-serialized internally)
Optionalcategory: stringOptional category for key isolation (defaults to "default")
Optionaloptions: number | LocalStorageWriteOptionsExpiry and index group. A bare number is accepted as the TTL in seconds.
Starts listening for cache change events from other server instances. Creates a dedicated Redis connection for pub/sub (required by Redis protocol — a client in subscribe mode cannot execute other commands).
Must be called explicitly after construction. No-op if enablePubSub is false
in the config. Concurrent and repeated calls share one start-up; if it fails, the
subscriber is discarded and the next call starts a fresh one.
Registers a handler for messages on a named channel, starting the subscriber if needed.
When pub/sub is disabled this registers nothing and returns an inert unsubscribe; check IsPubSubEnabled to tell that apart from a live subscription.
No echo suppression happens here — this layer does not know the payload shape. A publisher that needs it must stamp its own origin on the message and check it in the handler.
Redis does not replay messages, so anything published while the subscriber is disconnected is never delivered.
Logical channel name (unprefixed), namespaced as in PublishMessage.
Called with each message body. May be async.
A function that removes this handler. When the last handler on a channel is removed, the provider unsubscribes from it in Redis.
Claims {prefix}:__lease__:{name} for ttlMs with SET NX PX, so one process in the fleet
runs a periodic job per period. The lease is not released; it expires.
Runs work holding an exclusive lock on key across every process sharing this keyspace.
The lock is SET {prefix}:__lock__:{category}:{key} <token> NX PX 10000, retried every 20 ms
for up to 5 s, and released only by its holder (compare-and-delete). The 10 s expiry frees a
lock whose holder died; work is expected to be a short read-modify-write.
Redis-backed implementation of the MemberJunction ILocalStorageProvider interface.
Provides persistent, shared caching for server-side environments using Redis. This is a drop-in replacement for
InMemoryLocalStorageProvider— all consumers (likeLocalCacheManager,ProviderBasemetadata caching, etc.) work without any code changes.Key Structure
All keys follow the pattern:
{prefix}:{category}:{key}"mj"to isolate MJ data in shared Redis instancesRunViewCache,Metadata,DatasetCache, etc.)Keys written with an
IndexGroupare also recorded in a Redis Set at{prefix}:__group__:{category}:{group}(the RunView cache uses the entity name), whichGetIndexGroupKeys()reads and prunes.ClearCategory()andGetCategoryKeys()walk the keyspace withSCAN; no category-wide set is kept, because expiry never removes set members and such a set grows without bound once keys carry a TTL.TTL Support
Redis has native key expiration. The provider supports TTL at two levels:
defaultTTLSecondsin config — applied to allSetItemcallsttlSecondsparameter onSetItem— overrides the default per-callError Handling
Redis operations are wrapped in try/catch blocks. Connection errors are logged via
LogError()but do not throw — the provider gracefully returnsnullfor reads and silently skips writes. This prevents a Redis outage from crashing the application. Theioredisclient handles automatic reconnection.Example