@db-bridge/redis 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Berke Erdoğan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,468 @@
1
+ # @db-bridge/redis
2
+
3
+ Redis adapter and caching layer for DB Bridge - A comprehensive database management library.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @db-bridge/redis @db-bridge/core
9
+ ```
10
+
11
+ ## Features
12
+
13
+ - Full Redis command support via ioredis
14
+ - Caching layer for database adapters
15
+ - Automatic cache invalidation
16
+ - Pub/Sub support
17
+ - Batch operations
18
+ - TTL management
19
+ - Key pattern matching
20
+ - TypeScript support
21
+
22
+ ## Quick Start
23
+
24
+ ### As a Cache Adapter
25
+
26
+ ```typescript
27
+ import { RedisAdapter } from '@db-bridge/redis';
28
+
29
+ const cache = new RedisAdapter({
30
+ keyPrefix: 'myapp:',
31
+ ttl: 3600, // Default TTL: 1 hour
32
+ });
33
+
34
+ // Connect
35
+ await cache.connect({
36
+ host: 'localhost',
37
+ port: 6379,
38
+ password: 'your-password',
39
+ });
40
+
41
+ // Basic operations
42
+ await cache.set('user:123', { id: 123, name: 'John Doe' });
43
+ const user = await cache.get('user:123');
44
+
45
+ // Disconnect
46
+ await cache.disconnect();
47
+ ```
48
+
49
+ ### As a Database Cache Layer
50
+
51
+ ```typescript
52
+ import { MySQLAdapter } from '@db-bridge/mysql';
53
+ import { RedisAdapter, CachedAdapter } from '@db-bridge/redis';
54
+
55
+ // Setup adapters
56
+ const mysql = new MySQLAdapter();
57
+ const redis = new RedisAdapter({ keyPrefix: 'cache:' });
58
+
59
+ // Create cached adapter
60
+ const db = new CachedAdapter({
61
+ adapter: mysql,
62
+ cache: redis,
63
+ defaultTTL: 300, // 5 minutes
64
+ });
65
+
66
+ // Queries are automatically cached
67
+ const users = await db.query('SELECT * FROM users WHERE role = ?', ['admin'], {
68
+ cache: { key: 'admin-users', ttl: 600 },
69
+ });
70
+ ```
71
+
72
+ ## Configuration
73
+
74
+ ### Redis Connection Options
75
+
76
+ ```typescript
77
+ interface RedisConnectionConfig {
78
+ host?: string;
79
+ port?: number;
80
+ password?: string;
81
+ db?: number;
82
+ username?: string;
83
+ sentinels?: Array<{ host: string; port: number }>;
84
+ name?: string; // Sentinel master name
85
+ family?: 4 | 6;
86
+ path?: string; // Unix socket
87
+ keepAlive?: number;
88
+ connectionName?: string;
89
+ enableReadyCheck?: boolean;
90
+ enableOfflineQueue?: boolean;
91
+ connectTimeout?: number;
92
+ autoResubscribe?: boolean;
93
+ autoResendUnfulfilledCommands?: boolean;
94
+ lazyConnect?: boolean;
95
+ tls?: ConnectionOptions;
96
+ keyPrefix?: string;
97
+ retryStrategy?: (times: number) => number | void | null;
98
+ }
99
+ ```
100
+
101
+ ### Adapter Options
102
+
103
+ ```typescript
104
+ const redis = new RedisAdapter({
105
+ // Key prefix for all operations
106
+ keyPrefix: 'myapp:cache:',
107
+
108
+ // Default TTL in seconds
109
+ ttl: 3600,
110
+
111
+ // Logger instance
112
+ logger: console,
113
+
114
+ // Connection timeout
115
+ connectionTimeout: 5000,
116
+
117
+ // Command timeout
118
+ commandTimeout: 2500,
119
+
120
+ // Enable compression for large values
121
+ enableCompression: true,
122
+
123
+ // ioredis specific options
124
+ redis: {
125
+ maxRetriesPerRequest: 3,
126
+ enableAutoPipelining: true,
127
+ retryStrategy: (times) => Math.min(times * 50, 2000),
128
+ },
129
+ });
130
+ ```
131
+
132
+ ## Usage
133
+
134
+ ### Basic Cache Operations
135
+
136
+ ```typescript
137
+ // Set with TTL
138
+ await redis.set('session:abc123', { userId: 123, role: 'admin' }, 1800); // 30 minutes
139
+
140
+ // Get
141
+ const session = await redis.get('session:abc123');
142
+
143
+ // Check existence
144
+ const exists = await redis.exists('session:abc123');
145
+
146
+ // Delete
147
+ const deleted = await redis.delete('session:abc123');
148
+
149
+ // Get TTL
150
+ const ttl = await redis.ttl('session:abc123');
151
+
152
+ // Extend TTL
153
+ await redis.expire('session:abc123', 3600);
154
+
155
+ // Clear all with pattern
156
+ const keys = await redis.keys('session:*');
157
+ for (const key of keys) {
158
+ await redis.delete(key);
159
+ }
160
+ ```
161
+
162
+ ### Batch Operations
163
+
164
+ ```typescript
165
+ // Batch set
166
+ await redis.mset([
167
+ { key: 'user:1', value: { name: 'Alice' }, ttl: 3600 },
168
+ { key: 'user:2', value: { name: 'Bob' }, ttl: 3600 },
169
+ { key: 'user:3', value: { name: 'Charlie' }, ttl: 3600 },
170
+ ]);
171
+
172
+ // Batch get
173
+ const users = await redis.mget(['user:1', 'user:2', 'user:3']);
174
+
175
+ // Batch delete
176
+ await redis.mdel(['user:1', 'user:2', 'user:3']);
177
+ ```
178
+
179
+ ### Atomic Operations
180
+
181
+ ```typescript
182
+ // Increment/Decrement
183
+ const count = await redis.increment('page:views', 1);
184
+ const remaining = await redis.decrement('api:quota', 1);
185
+
186
+ // Atomic compare and swap
187
+ const key = 'resource:lock';
188
+ const token = 'unique-token';
189
+ const acquired = await redis.setnx(key, token, 30); // Lock for 30 seconds
190
+
191
+ if (acquired) {
192
+ try {
193
+ // Do work
194
+ } finally {
195
+ // Release lock only if we own it
196
+ const current = await redis.get(key);
197
+ if (current === token) {
198
+ await redis.delete(key);
199
+ }
200
+ }
201
+ }
202
+ ```
203
+
204
+ ### Database Caching
205
+
206
+ ```typescript
207
+ // Create cached database adapter
208
+ const cachedDb = new CachedAdapter({
209
+ adapter: mysql,
210
+ cache: redis,
211
+ defaultTTL: 300,
212
+ strategy: 'lazy', // 'lazy' | 'eager' | 'refresh'
213
+ cacheableCommands: ['SELECT'], // Only cache SELECT queries
214
+ logger: console,
215
+ });
216
+
217
+ // Automatic caching with custom key
218
+ const products = await cachedDb.query(
219
+ 'SELECT * FROM products WHERE category = ?',
220
+ ['electronics'],
221
+ {
222
+ cache: {
223
+ key: 'products:electronics',
224
+ ttl: 600,
225
+ tags: ['products', 'electronics'],
226
+ },
227
+ },
228
+ );
229
+
230
+ // Disable cache for specific query
231
+ const realtimeData = await cachedDb.query('SELECT * FROM active_sessions', [], { cache: false });
232
+
233
+ // Manual cache invalidation
234
+ const cacheManager = cachedDb.getCacheManager();
235
+ await cacheManager.invalidate(['products:electronics']);
236
+ await cacheManager.invalidateByTags(['products']);
237
+ await cacheManager.invalidateAll();
238
+ ```
239
+
240
+ ### Redis Commands
241
+
242
+ Access all Redis commands through the `commands` property:
243
+
244
+ ```typescript
245
+ const redis = new RedisAdapter();
246
+ const commands = redis.commands;
247
+
248
+ // Strings
249
+ await commands.set('key', 'value');
250
+ await commands.get('key');
251
+ await commands.mget('key1', 'key2');
252
+ await commands.incr('counter');
253
+ await commands.incrby('counter', 5);
254
+
255
+ // Hashes
256
+ await commands.hset('user:123', 'name', 'John');
257
+ await commands.hget('user:123', 'name');
258
+ await commands.hgetall('user:123');
259
+ await commands.hmset('user:123', { name: 'John', age: 30 });
260
+
261
+ // Lists
262
+ await commands.lpush('queue', 'task1', 'task2');
263
+ await commands.rpop('queue');
264
+ await commands.lrange('queue', 0, -1);
265
+ await commands.llen('queue');
266
+
267
+ // Sets
268
+ await commands.sadd('tags', 'nodejs', 'redis');
269
+ await commands.srem('tags', 'redis');
270
+ await commands.smembers('tags');
271
+ await commands.sismember('tags', 'nodejs');
272
+ await commands.scard('tags');
273
+
274
+ // Sorted Sets
275
+ await commands.zadd('leaderboard', 100, 'player1', 200, 'player2');
276
+ await commands.zrange('leaderboard', 0, -1);
277
+ await commands.zrevrange('leaderboard', 0, 9, 'WITHSCORES');
278
+ await commands.zscore('leaderboard', 'player1');
279
+ await commands.zrank('leaderboard', 'player1');
280
+
281
+ // Pub/Sub
282
+ await commands.subscribe('channel1', 'channel2');
283
+ await commands.publish('channel1', 'Hello World');
284
+ commands.on('message', (channel, message) => {
285
+ console.log(`Received ${message} from ${channel}`);
286
+ });
287
+
288
+ // Transactions
289
+ const multi = commands.multi();
290
+ multi.set('key1', 'value1');
291
+ multi.set('key2', 'value2');
292
+ multi.get('key1');
293
+ const results = await multi.exec();
294
+
295
+ // Lua Scripts
296
+ const script = `
297
+ local current = redis.call('get', KEYS[1])
298
+ if current == ARGV[1] then
299
+ redis.call('set', KEYS[1], ARGV[2])
300
+ return 1
301
+ else
302
+ return 0
303
+ end
304
+ `;
305
+ const result = await commands.eval(script, 1, 'key', 'oldValue', 'newValue');
306
+ ```
307
+
308
+ ## Advanced Features
309
+
310
+ ### Cache Invalidation Strategies
311
+
312
+ ```typescript
313
+ // Tag-based invalidation
314
+ await cachedDb.query('SELECT * FROM products WHERE category = ?', ['electronics'], {
315
+ cache: { tags: ['products', 'electronics'] },
316
+ });
317
+
318
+ // Invalidate by tag
319
+ await cacheManager.invalidateByTags(['electronics']);
320
+
321
+ // Pattern-based invalidation
322
+ await cacheManager.invalidatePattern('products:*');
323
+
324
+ // Automatic invalidation on mutations
325
+ const result = await cachedDb.execute('UPDATE products SET price = ? WHERE id = ?', [99.99, 123]); // Automatically invalidates related cache
326
+ ```
327
+
328
+ ### Cache Warming
329
+
330
+ ```typescript
331
+ // Pre-warm cache on startup
332
+ const warmupQueries = [
333
+ { sql: 'SELECT * FROM config', key: 'app:config', ttl: 86400 },
334
+ { sql: 'SELECT * FROM categories', key: 'categories:all', ttl: 3600 },
335
+ ];
336
+
337
+ for (const query of warmupQueries) {
338
+ const result = await mysql.query(query.sql);
339
+ await redis.set(query.key, result.rows, query.ttl);
340
+ }
341
+ ```
342
+
343
+ ### Monitoring and Statistics
344
+
345
+ ```typescript
346
+ // Cache statistics
347
+ const stats = cacheManager.getStatistics();
348
+ console.log('Cache hit rate:', stats.hitRate);
349
+ console.log('Total hits:', stats.hits);
350
+ console.log('Total misses:', stats.misses);
351
+ console.log('Total cached:', stats.totalCached);
352
+
353
+ // Redis info
354
+ const info = await redis.info();
355
+ console.log('Memory usage:', info.memory.used_memory_human);
356
+ console.log('Connected clients:', info.clients.connected_clients);
357
+
358
+ // Monitor slow queries
359
+ redis.on('slowlog', (log) => {
360
+ console.warn('Slow Redis command:', log);
361
+ });
362
+ ```
363
+
364
+ ### Clustering Support
365
+
366
+ ```typescript
367
+ // Redis Cluster
368
+ const redis = new RedisAdapter({
369
+ cluster: [
370
+ { host: 'redis1', port: 6379 },
371
+ { host: 'redis2', port: 6379 },
372
+ { host: 'redis3', port: 6379 },
373
+ ],
374
+ clusterOptions: {
375
+ enableReadyCheck: true,
376
+ maxRedirections: 16,
377
+ retryDelayOnFailover: 100,
378
+ retryDelayOnClusterDown: 300,
379
+ },
380
+ });
381
+
382
+ // Sentinel
383
+ const redis = new RedisAdapter({
384
+ sentinels: [
385
+ { host: 'sentinel1', port: 26379 },
386
+ { host: 'sentinel2', port: 26379 },
387
+ { host: 'sentinel3', port: 26379 },
388
+ ],
389
+ name: 'mymaster',
390
+ sentinelPassword: 'sentinel-password',
391
+ });
392
+ ```
393
+
394
+ ## Error Handling
395
+
396
+ ```typescript
397
+ import { ConnectionError, CacheError } from '@db-bridge/redis';
398
+
399
+ try {
400
+ await redis.connect(config);
401
+ } catch (error) {
402
+ if (error instanceof ConnectionError) {
403
+ console.error('Redis connection failed:', error.message);
404
+ // Fallback to database-only mode
405
+ }
406
+ }
407
+
408
+ // Handle cache misses gracefully
409
+ try {
410
+ const data = await redis.get('key');
411
+ if (!data) {
412
+ // Cache miss - fetch from database
413
+ const dbData = await fetchFromDatabase();
414
+ await redis.set('key', dbData, 300);
415
+ return dbData;
416
+ }
417
+ return data;
418
+ } catch (error) {
419
+ if (error instanceof CacheError) {
420
+ // Log error but continue with database
421
+ console.error('Cache error:', error);
422
+ return fetchFromDatabase();
423
+ }
424
+ }
425
+ ```
426
+
427
+ ## Best Practices
428
+
429
+ 1. **Use Key Prefixes**: Namespace your keys to avoid collisions
430
+ 2. **Set TTLs**: Always set appropriate TTLs to prevent memory bloat
431
+ 3. **Handle Cache Misses**: Implement fallback to database
432
+ 4. **Monitor Memory**: Keep track of Redis memory usage
433
+ 5. **Use Pipelining**: Batch operations when possible
434
+ 6. **Implement Circuit Breaker**: Protect against Redis failures
435
+
436
+ ## TypeScript Support
437
+
438
+ ```typescript
439
+ interface CachedUser {
440
+ id: number;
441
+ name: string;
442
+ email: string;
443
+ preferences: {
444
+ theme: 'light' | 'dark';
445
+ notifications: boolean;
446
+ };
447
+ }
448
+
449
+ // Type-safe cache operations
450
+ await redis.set<CachedUser>('user:123', {
451
+ id: 123,
452
+ name: 'John Doe',
453
+ email: 'john@example.com',
454
+ preferences: {
455
+ theme: 'dark',
456
+ notifications: true,
457
+ },
458
+ });
459
+
460
+ const user = await redis.get<CachedUser>('user:123');
461
+ if (user) {
462
+ console.log(user.preferences.theme); // TypeScript knows the type
463
+ }
464
+ ```
465
+
466
+ ## License
467
+
468
+ MIT © Berke Erdoğan