1.2.1 • Published 3d ago
@db-bridge/redis
Licence
MIT
Version
1.2.1
Deps
1
Size
200 kB
Vulns
0
Weekly
0
@db-bridge/redis
Redis adapter and caching layer for DB Bridge - A comprehensive database management library.
Installation
npm install @db-bridge/redis @db-bridge/core
Features
- Full Redis command support via ioredis
- Caching layer for database adapters
- Automatic cache invalidation
- Pub/Sub support
- Batch operations
- TTL management
- Key pattern matching
- TypeScript support
Quick Start
As a Cache Adapter
import { RedisAdapter } from '@db-bridge/redis';
const cache = new RedisAdapter({
keyPrefix: 'myapp:',
ttl: 3600, // Default TTL: 1 hour
});
// Connect
await cache.connect({
host: 'localhost',
port: 6379,
password: 'your-password',
});
// Basic operations
await cache.set('user:123', { id: 123, name: 'John Doe' });
const user = await cache.get('user:123');
// Disconnect
await cache.disconnect();
As a Database Cache Layer
import { MySQLAdapter } from '@db-bridge/mysql';
import { RedisAdapter, CachedAdapter } from '@db-bridge/redis';
// Setup adapters
const mysql = new MySQLAdapter();
const redis = new RedisAdapter({ keyPrefix: 'cache:' });
// Create cached adapter
const db = new CachedAdapter({
adapter: mysql,
cache: redis,
defaultTTL: 300, // 5 minutes
});
// Queries are automatically cached
const users = await db.query('SELECT * FROM users WHERE role = ?', ['admin'], {
cache: { key: 'admin-users', ttl: 600 },
});
Configuration
Redis Connection Options
interface RedisConnectionConfig {
host?: string;
port?: number;
password?: string;
db?: number;
username?: string;
sentinels?: Array<{ host: string; port: number }>;
name?: string; // Sentinel master name
family?: 4 | 6;
path?: string; // Unix socket
keepAlive?: number;
connectionName?: string;
enableReadyCheck?: boolean;
enableOfflineQueue?: boolean;
connectTimeout?: number;
autoResubscribe?: boolean;
autoResendUnfulfilledCommands?: boolean;
lazyConnect?: boolean;
tls?: ConnectionOptions;
keyPrefix?: string;
retryStrategy?: (times: number) => number | void | null;
}
Adapter Options
const redis = new RedisAdapter({
// Key prefix for all operations
keyPrefix: 'myapp:cache:',
// Default TTL in seconds
ttl: 3600,
// Logger instance
logger: console,
// Connection timeout
connectionTimeout: 5000,
// Command timeout
commandTimeout: 2500,
// Enable compression for large values
enableCompression: true,
// ioredis specific options
redis: {
maxRetriesPerRequest: 3,
enableAutoPipelining: true,
retryStrategy: (times) => Math.min(times * 50, 2000),
},
});
Usage
Basic Cache Operations
// Set with TTL
await redis.set('session:abc123', { userId: 123, role: 'admin' }, 1800); // 30 minutes
// Get
const session = await redis.get('session:abc123');
// Check existence
const exists = await redis.exists('session:abc123');
// Delete
const deleted = await redis.delete('session:abc123');
// Get TTL
const ttl = await redis.ttl('session:abc123');
// Extend TTL
await redis.expire('session:abc123', 3600);
// Clear all with pattern
const keys = await redis.keys('session:*');
for (const key of keys) {
await redis.delete(key);
}
Batch Operations
// Batch set
await redis.mset([
{ key: 'user:1', value: { name: 'Alice' }, ttl: 3600 },
{ key: 'user:2', value: { name: 'Bob' }, ttl: 3600 },
{ key: 'user:3', value: { name: 'Charlie' }, ttl: 3600 },
]);
// Batch get
const users = await redis.mget(['user:1', 'user:2', 'user:3']);
// Batch delete
await redis.mdel(['user:1', 'user:2', 'user:3']);
Atomic Operations
// Increment/Decrement
const count = await redis.increment('page:views', 1);
const remaining = await redis.decrement('api:quota', 1);
// Atomic compare and swap
const key = 'resource:lock';
const token = 'unique-token';
const acquired = await redis.setnx(key, token, 30); // Lock for 30 seconds
if (acquired) {
try {
// Do work
} finally {
// Release lock only if we own it
const current = await redis.get(key);
if (current === token) {
await redis.delete(key);
}
}
}
Database Caching
// Create cached database adapter
const cachedDb = new CachedAdapter({
adapter: mysql,
cache: redis,
defaultTTL: 300,
strategy: 'lazy', // 'lazy' | 'eager' | 'refresh'
cacheableCommands: ['SELECT'], // Only cache SELECT queries
logger: console,
});
// Automatic caching with custom key
const products = await cachedDb.query(
'SELECT * FROM products WHERE category = ?',
['electronics'],
{
cache: {
key: 'products:electronics',
ttl: 600,
tags: ['products', 'electronics'],
},
},
);
// Disable cache for specific query
const realtimeData = await cachedDb.query('SELECT * FROM active_sessions', [], { cache: false });
// Manual cache invalidation
const cacheManager = cachedDb.getCacheManager();
await cacheManager.invalidate(['products:electronics']);
await cacheManager.invalidateByTags(['products']);
await cacheManager.invalidateAll();
Redis Commands
Access all Redis commands through the commands property:
const redis = new RedisAdapter();
const commands = redis.commands;
// Strings
await commands.set('key', 'value');
await commands.get('key');
await commands.mget('key1', 'key2');
await commands.incr('counter');
await commands.incrby('counter', 5);
// Hashes
await commands.hset('user:123', 'name', 'John');
await commands.hget('user:123', 'name');
await commands.hgetall('user:123');
await commands.hmset('user:123', { name: 'John', age: 30 });
// Lists
await commands.lpush('queue', 'task1', 'task2');
await commands.rpop('queue');
await commands.lrange('queue', 0, -1);
await commands.llen('queue');
// Sets
await commands.sadd('tags', 'nodejs', 'redis');
await commands.srem('tags', 'redis');
await commands.smembers('tags');
await commands.sismember('tags', 'nodejs');
await commands.scard('tags');
// Sorted Sets
await commands.zadd('leaderboard', 100, 'player1', 200, 'player2');
await commands.zrange('leaderboard', 0, -1);
await commands.zrevrange('leaderboard', 0, 9, 'WITHSCORES');
await commands.zscore('leaderboard', 'player1');
await commands.zrank('leaderboard', 'player1');
// Pub/Sub
await commands.subscribe('channel1', 'channel2');
await commands.publish('channel1', 'Hello World');
commands.on('message', (channel, message) => {
console.log(`Received ${message} from ${channel}`);
});
// Transactions
const multi = commands.multi();
multi.set('key1', 'value1');
multi.set('key2', 'value2');
multi.get('key1');
const results = await multi.exec();
// Lua Scripts
const script = `
local current = redis.call('get', KEYS[1])
if current == ARGV[1] then
redis.call('set', KEYS[1], ARGV[2])
return 1
else
return 0
end
`;
const result = await commands.eval(script, 1, 'key', 'oldValue', 'newValue');
Advanced Features
Cache Invalidation Strategies
// Tag-based invalidation
await cachedDb.query('SELECT * FROM products WHERE category = ?', ['electronics'], {
cache: { tags: ['products', 'electronics'] },
});
// Invalidate by tag
await cacheManager.invalidateByTags(['electronics']);
// Pattern-based invalidation
await cacheManager.invalidatePattern('products:*');
// Automatic invalidation on mutations
const result = await cachedDb.execute('UPDATE products SET price = ? WHERE id = ?', [99.99, 123]); // Automatically invalidates related cache
Cache Warming
// Pre-warm cache on startup
const warmupQueries = [
{ sql: 'SELECT * FROM config', key: 'app:config', ttl: 86400 },
{ sql: 'SELECT * FROM categories', key: 'categories:all', ttl: 3600 },
];
for (const query of warmupQueries) {
const result = await mysql.query(query.sql);
await redis.set(query.key, result.rows, query.ttl);
}
Monitoring and Statistics
// Cache statistics
const stats = cacheManager.getStatistics();
console.log('Cache hit rate:', stats.hitRate);
console.log('Total hits:', stats.hits);
console.log('Total misses:', stats.misses);
console.log('Total cached:', stats.totalCached);
// Redis info
const info = await redis.info();
console.log('Memory usage:', info.memory.used_memory_human);
console.log('Connected clients:', info.clients.connected_clients);
// Monitor slow queries
redis.on('slowlog', (log) => {
console.warn('Slow Redis command:', log);
});
Clustering Support
// Redis Cluster
const redis = new RedisAdapter({
cluster: [
{ host: 'redis1', port: 6379 },
{ host: 'redis2', port: 6379 },
{ host: 'redis3', port: 6379 },
],
clusterOptions: {
enableReadyCheck: true,
maxRedirections: 16,
retryDelayOnFailover: 100,
retryDelayOnClusterDown: 300,
},
});
// Sentinel
const redis = new RedisAdapter({
sentinels: [
{ host: 'sentinel1', port: 26379 },
{ host: 'sentinel2', port: 26379 },
{ host: 'sentinel3', port: 26379 },
],
name: 'mymaster',
sentinelPassword: 'sentinel-password',
});
Error Handling
import { ConnectionError, CacheError } from '@db-bridge/redis';
try {
await redis.connect(config);
} catch (error) {
if (error instanceof ConnectionError) {
console.error('Redis connection failed:', error.message);
// Fallback to database-only mode
}
}
// Handle cache misses gracefully
try {
const data = await redis.get('key');
if (!data) {
// Cache miss - fetch from database
const dbData = await fetchFromDatabase();
await redis.set('key', dbData, 300);
return dbData;
}
return data;
} catch (error) {
if (error instanceof CacheError) {
// Log error but continue with database
console.error('Cache error:', error);
return fetchFromDatabase();
}
}
Best Practices
- Use Key Prefixes: Namespace your keys to avoid collisions
- Set TTLs: Always set appropriate TTLs to prevent memory bloat
- Handle Cache Misses: Implement fallback to database
- Monitor Memory: Keep track of Redis memory usage
- Use Pipelining: Batch operations when possible
- Implement Circuit Breaker: Protect against Redis failures
TypeScript Support
interface CachedUser {
id: number;
name: string;
email: string;
preferences: {
theme: 'light' | 'dark';
notifications: boolean;
};
}
// Type-safe cache operations
await redis.set<CachedUser>('user:123', {
id: 123,
name: 'John Doe',
email: 'john@example.com',
preferences: {
theme: 'dark',
notifications: true,
},
});
const user = await redis.get<CachedUser>('user:123');
if (user) {
console.log(user.preferences.theme); // TypeScript knows the type
}
License
MIT Berke Erdoğan