@memberjunction/redis-provider 0.0.1 → 5.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,45 +1,324 @@
1
1
  # @memberjunction/redis-provider
2
2
 
3
- ## ⚠️ IMPORTANT NOTICE ⚠️
3
+ Redis-backed implementation of MemberJunction's `ILocalStorageProvider` interface. Enables persistent, shared server-side caching via any Redis-compatible service — self-hosted Redis, Azure Managed Redis, AWS ElastiCache, Redis Cloud, Upstash, or any other Redis-protocol endpoint.
4
4
 
5
- **This package is created solely for the purpose of setting up OIDC (OpenID Connect) trusted publishing with npm.**
5
+ ## Why Redis?
6
6
 
7
- This is **NOT** a functional package and contains **NO** code or functionality beyond the OIDC setup configuration.
7
+ MemberJunction's default server-side cache (`InMemoryLocalStorageProvider`) stores data in a plain `Map` inside the Node.js process. This works well for single-server development but has two limitations in production:
8
8
 
9
- ## Purpose
9
+ | Limitation | Impact |
10
+ |-----------|--------|
11
+ | **Not shared** | Each MJAPI instance has its own cache — no benefit from horizontal scaling |
12
+ | **Not persistent** | Cache is lost on process restart — cold starts hit the database for everything |
10
13
 
11
- This package exists to:
12
- 1. Configure OIDC trusted publishing for the package name `@memberjunction/redis-provider`
13
- 2. Enable secure, token-less publishing from CI/CD workflows
14
- 3. Establish provenance for packages published under this name
14
+ A Redis-backed provider solves both problems while remaining a drop-in replacement — no changes to `LocalCacheManager`, `ProviderBase`, or any consumer code.
15
15
 
16
- ## What is OIDC Trusted Publishing?
16
+ ## Installation
17
17
 
18
- OIDC trusted publishing allows package maintainers to publish packages directly from their CI/CD workflows without needing to manage npm access tokens. Instead, it uses OpenID Connect to establish trust between the CI/CD provider (like GitHub Actions) and npm.
18
+ ```bash
19
+ # Add to the package that configures your data provider (typically MJAPI or your server bootstrap)
20
+ # Then run npm install at the repo root
21
+ npm install @memberjunction/redis-provider
22
+ ```
19
23
 
20
- ## Setup Instructions
24
+ > **Monorepo note:** In the MemberJunction monorepo, add the dependency to the relevant package's `package.json` and run `npm install` at the repo root.
21
25
 
22
- To properly configure OIDC trusted publishing for this package:
26
+ ## Quick Start
23
27
 
24
- 1. Go to [npmjs.com](https://www.npmjs.com/) and navigate to your package settings
25
- 2. Configure the trusted publisher (e.g., GitHub Actions)
26
- 3. Specify the repository and workflow that should be allowed to publish
27
- 4. Use the configured workflow to publish your actual package
28
+ ```typescript
29
+ import { RedisLocalStorageProvider } from '@memberjunction/redis-provider';
30
+ import { Metadata } from '@memberjunction/core';
31
+ import type { GenericDatabaseProvider } from '@memberjunction/generic-database-provider';
28
32
 
29
- ## DO NOT USE THIS PACKAGE
33
+ // 1. Create the Redis provider
34
+ const redisProvider = new RedisLocalStorageProvider({
35
+ url: 'redis://localhost:6379',
36
+ defaultTTLSeconds: 300, // 5-minute default TTL
37
+ });
30
38
 
31
- This package is a placeholder for OIDC configuration only. It:
32
- - Contains no executable code
33
- - Provides no functionality
34
- - Should not be installed as a dependency
35
- - Exists only for administrative purposes
39
+ // 2. Inject it into the data provider
40
+ const provider = Metadata.Provider as GenericDatabaseProvider;
41
+ provider.SetLocalStorageProvider(redisProvider);
36
42
 
37
- ## More Information
43
+ // That's it! All MJ caching now flows through Redis.
44
+ ```
38
45
 
39
- For more details about npm's trusted publishing feature, see:
40
- - [npm Trusted Publishing Documentation](https://docs.npmjs.com/generating-provenance-statements)
41
- - [GitHub Actions OIDC Documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)
46
+ ## Configuration
42
47
 
43
- ---
48
+ The `RedisProviderConfig` object supports the following options:
44
49
 
45
- **Maintained for OIDC setup purposes only**
50
+ | Option | Type | Default | Description |
51
+ |--------|------|---------|-------------|
52
+ | `url` | `string` | — | Redis connection URL (`redis://` or `rediss://` for TLS). Mutually exclusive with `options`. |
53
+ | `options` | `RedisOptions` | — | Full [`ioredis` options](https://github.com/redis/ioredis#connect-to-redis) object. Mutually exclusive with `url`. |
54
+ | `keyPrefix` | `string` | `'mj'` | Prefix for all Redis keys. Useful for isolating MJ data in a shared Redis instance. |
55
+ | `defaultTTLSeconds` | `number` | `undefined` | Default time-to-live for all cached entries. `undefined` means keys persist until explicitly removed. |
56
+ | `maxRetries` | `number` | `10` | Maximum reconnection attempts with exponential backoff before giving up. |
57
+ | `enableLogging` | `boolean` | `true` | Whether to log connection events via MJ's `LogStatus`/`LogError`. |
58
+
59
+ ### Connection Examples
60
+
61
+ #### Local Development (Docker)
62
+
63
+ ```bash
64
+ # Start a local Redis container
65
+ docker run -d --name mj-redis -p 6379:6379 redis:7-alpine
66
+ ```
67
+
68
+ ```typescript
69
+ const provider = new RedisLocalStorageProvider({
70
+ url: 'redis://localhost:6379',
71
+ defaultTTLSeconds: 300,
72
+ });
73
+ ```
74
+
75
+ #### Azure Managed Redis
76
+
77
+ ```typescript
78
+ const provider = new RedisLocalStorageProvider({
79
+ url: `rediss://default:${process.env.AZURE_REDIS_KEY}@${process.env.AZURE_REDIS_HOST}:6380`,
80
+ defaultTTLSeconds: 600,
81
+ });
82
+ ```
83
+
84
+ Or using the options object for more control:
85
+
86
+ ```typescript
87
+ const provider = new RedisLocalStorageProvider({
88
+ options: {
89
+ host: process.env.AZURE_REDIS_HOST,
90
+ port: 6380,
91
+ password: process.env.AZURE_REDIS_KEY,
92
+ tls: {}, // Required for Azure
93
+ db: 0,
94
+ },
95
+ defaultTTLSeconds: 600,
96
+ });
97
+ ```
98
+
99
+ #### AWS ElastiCache
100
+
101
+ ```typescript
102
+ const provider = new RedisLocalStorageProvider({
103
+ options: {
104
+ host: 'my-cluster.abc123.use1.cache.amazonaws.com',
105
+ port: 6379,
106
+ tls: {}, // Required for encryption in transit
107
+ },
108
+ defaultTTLSeconds: 600,
109
+ });
110
+ ```
111
+
112
+ #### Redis Cloud / Upstash
113
+
114
+ ```typescript
115
+ const provider = new RedisLocalStorageProvider({
116
+ url: process.env.REDIS_URL, // Provided by the service
117
+ defaultTTLSeconds: 600,
118
+ });
119
+ ```
120
+
121
+ ## Architecture
122
+
123
+ ### How It Fits Into MemberJunction
124
+
125
+ ```
126
+ ┌─────────────────────────────────────────────────────────────┐
127
+ │ Application Layer │
128
+ │ (MJAPI, Angular, React, Custom Apps) │
129
+ └────────────────────────┬────────────────────────────────────┘
130
+
131
+
132
+ ┌──────────────────────┐
133
+ │ LocalCacheManager │ Singleton — LRU eviction, TTL,
134
+ │ (MJCore) │ stats, category-based isolation
135
+ └──────────┬───────────┘
136
+
137
+
138
+ ┌──────────────────────┐
139
+ │ ILocalStorageProvider│ Abstract interface (MJCore)
140
+ └──────────┬───────────┘
141
+
142
+ ┌────────────┼────────────────┐
143
+ │ │ │
144
+ ▼ ▼ ▼
145
+ ┌──────────────┐ ┌──────────┐ ┌────────────────┐
146
+ │ InMemory │ │ Browser │ │ Redis │
147
+ │ (default) │ │ (IDB/LS) │ │ (this package) │
148
+ └──────────────┘ └──────────┘ └────────┬───────┘
149
+
150
+
151
+ ┌─────────────┐
152
+ │ Redis Server │
153
+ │ (any host) │
154
+ └─────────────┘
155
+ ```
156
+
157
+ ### Key Structure
158
+
159
+ All keys follow the pattern: `{prefix}:{category}:{key}`
160
+
161
+ - **prefix** — Configurable (default `"mj"`), isolates MJ data in shared Redis instances
162
+ - **category** — Maps to MJ cache categories: `RunViewCache`, `Metadata`, `DatasetCache`, `RunQueryCache`, `default`
163
+ - **key** — The original key from the calling code
164
+
165
+ Example Redis keys:
166
+ ```
167
+ mj:RunViewCache:Users|Active=1|Name ASC
168
+ mj:Metadata:___MJCore_Metadata_AllMetadata
169
+ mj:DatasetCache:MyDataset_items
170
+ mj:default:some-arbitrary-key
171
+ ```
172
+
173
+ ### Category Tracking
174
+
175
+ Each category has an associated Redis Set at `{prefix}:__categories__:{category}` that tracks all member keys. This enables efficient:
176
+
177
+ - **`ClearCategory()`** — Deletes all keys in a category in a single pipeline
178
+ - **`GetCategoryKeys()`** — Lists all keys without scanning the entire keyspace
179
+
180
+ ### TTL (Time-to-Live)
181
+
182
+ Redis has native key expiration, so TTL is handled efficiently at the server level:
183
+
184
+ 1. **Config default** — `defaultTTLSeconds` applies to every `SetItem()` call
185
+ 2. **Per-call override** — `SetItem(key, value, category, ttlSeconds)` overrides the default
186
+ 3. **No TTL** — If neither is set, keys persist until explicitly removed or `ClearCategory()` is called
187
+
188
+ ### Error Handling
189
+
190
+ All Redis operations are wrapped in try/catch. On failure:
191
+ - **Reads** return `null` (cache miss, falls through to database)
192
+ - **Writes** are silently skipped (data stays in the database, just not cached)
193
+ - **Connection errors** are logged via `LogError()` but don't crash the app
194
+ - **Reconnection** is automatic via `ioredis` with configurable exponential backoff
195
+
196
+ This design ensures a Redis outage degrades performance (more database hits) but never causes application downtime.
197
+
198
+ ## API Reference
199
+
200
+ ### `RedisLocalStorageProvider`
201
+
202
+ #### Constructor
203
+
204
+ ```typescript
205
+ new RedisLocalStorageProvider(config?: RedisProviderConfig)
206
+ ```
207
+
208
+ Creates a new provider and establishes a Redis connection. The connection is lazy — it happens on the first command, so construction itself does not block.
209
+
210
+ #### ILocalStorageProvider Methods
211
+
212
+ | Method | Description |
213
+ |--------|-------------|
214
+ | `GetItem(key, category?)` | Retrieves a cached value. Returns `null` on miss or error. |
215
+ | `SetItem(key, value, category?, ttlSeconds?)` | Stores a value with optional TTL. Uses pipeline for atomic set + category tracking. |
216
+ | `Remove(key, category?)` | Deletes a key and removes it from category tracking. |
217
+ | `ClearCategory(category)` | Deletes all keys in a category using the tracking Set. |
218
+ | `GetCategoryKeys(category)` | Returns all key names in a category. |
219
+
220
+ #### Additional Methods
221
+
222
+ | Method | Description |
223
+ |--------|-------------|
224
+ | `Exists(key, category?)` | Checks key existence without transferring the value (more efficient than `GetItem`). |
225
+ | `GetTTL(key, category?)` | Returns remaining TTL in seconds (`-1` = no expiry, `-2` = key doesn't exist). |
226
+ | `Ping()` | Health check — returns `true` if Redis responds with `PONG`. |
227
+ | `Disconnect()` | Graceful shutdown — sends `QUIT` and waits for pending replies. |
228
+
229
+ #### Properties
230
+
231
+ | Property | Type | Description |
232
+ |----------|------|-------------|
233
+ | `IsConnected` | `boolean` | Whether the client has an active connection (transient — auto-reconnects). |
234
+ | `Client` | `Redis` | The underlying `ioredis` client for advanced operations (pub/sub, streams, etc.). |
235
+
236
+ ## Testing
237
+
238
+ ### Unit Tests (No Redis Required)
239
+
240
+ The package includes comprehensive unit tests with mocked Redis:
241
+
242
+ ```bash
243
+ cd packages/RedisProvider
244
+ npm run test
245
+ ```
246
+
247
+ ### Integration Testing with Local Redis
248
+
249
+ For end-to-end testing with a real Redis instance:
250
+
251
+ ```bash
252
+ # Start Redis
253
+ docker run -d --name mj-redis -p 6379:6379 redis:7-alpine
254
+
255
+ # Verify connectivity
256
+ docker exec mj-redis redis-cli ping
257
+ # → PONG
258
+
259
+ # Run your MJAPI with Redis configured
260
+ REDIS_URL=redis://localhost:6379 npm run start:api
261
+
262
+ # Monitor Redis activity in real time
263
+ docker exec mj-redis redis-cli monitor
264
+
265
+ # Clean up
266
+ docker stop mj-redis && docker rm mj-redis
267
+ ```
268
+
269
+ ### Monitoring Redis Usage
270
+
271
+ ```bash
272
+ # See all MJ keys
273
+ docker exec mj-redis redis-cli KEYS "mj:*"
274
+
275
+ # Check memory usage
276
+ docker exec mj-redis redis-cli INFO memory
277
+
278
+ # See cache hit/miss stats
279
+ docker exec mj-redis redis-cli INFO stats | grep keyspace
280
+ ```
281
+
282
+ ## Production Recommendations
283
+
284
+ ### TTL Strategy
285
+
286
+ | Category | Recommended TTL | Rationale |
287
+ |----------|----------------|-----------|
288
+ | `Metadata` | 30–60 minutes | Entity schema changes infrequently |
289
+ | `RunViewCache` | 2–5 minutes | Balance freshness vs. database load |
290
+ | `RunQueryCache` | 2–5 minutes | Same as RunViewCache |
291
+ | `DatasetCache` | 5–10 minutes | Datasets are typically larger, change less often |
292
+
293
+ ### Memory Management
294
+
295
+ - Set `maxmemory` and `maxmemory-policy allkeys-lru` in your Redis configuration so Redis automatically evicts least-recently-used keys when memory is full
296
+ - Monitor with `INFO memory` and set alerts on `used_memory_peak`
297
+ - Use `defaultTTLSeconds` to ensure keys don't accumulate indefinitely
298
+
299
+ ### High Availability
300
+
301
+ - **Azure Managed Redis**: Use Standard or Premium tier for replication
302
+ - **AWS ElastiCache**: Enable Multi-AZ with automatic failover
303
+ - **Self-hosted**: Use Redis Sentinel or Redis Cluster for HA
304
+
305
+ ### Security
306
+
307
+ - Always use TLS (`rediss://` URL scheme or `tls: {}` in options) for cloud-hosted Redis
308
+ - Use strong passwords and rotate them regularly
309
+ - Restrict network access to Redis (VNet/VPC peering, security groups)
310
+ - Never expose Redis ports to the public internet
311
+
312
+ ## Dependencies
313
+
314
+ - [`ioredis`](https://github.com/redis/ioredis) — Feature-rich Redis client for Node.js
315
+ - `@memberjunction/core` — `ILocalStorageProvider` interface and logging utilities
316
+ - `@memberjunction/global` — Global object store utilities
317
+
318
+ ## Related Packages
319
+
320
+ - [`@memberjunction/core`](../MJCore/) — Defines `ILocalStorageProvider`, `LocalCacheManager`, and `InMemoryLocalStorageProvider`
321
+ - [`@memberjunction/generic-database-provider`](../GenericDatabaseProvider/) — Where `LocalStorageProvider` is wired into the data provider chain
322
+ - [`@memberjunction/sqlserver-dataprovider`](../SQLServerDataProvider/) — SQL Server provider (inherits caching from GenericDatabaseProvider)
323
+ - [`@memberjunction/postgresql-dataprovider`](../PostgreSQLDataProvider/) — PostgreSQL provider (inherits caching from GenericDatabaseProvider)
324
+ - [**Caching & Pub/Sub Guide**](/guides/CACHING_AND_PUBSUB_GUIDE.md) — Comprehensive architecture guide covering Redis cross-server sync, GraphQL cache invalidation, deployment topologies, and troubleshooting