koatty_store
v1.9.0
Published
Cache store for koatty.
Maintainers
Readme
koatty_store
Cache store (memory or redis) for Koatty framework.
Features
- 🚀 Dual Storage: Support both in-memory and Redis storage
- 💾 LRU Cache: Built-in LRU cache with configurable size
- ⏰ TTL Support: Field-level TTL for hash operations
- 🔒 Concurrency Safe: Atomic operations with lock protection
- 📊 Rich Data Types: String, Hash, List, Set, Sorted Set
- 🔄 Auto Reconnect: Redis connection with retry mechanism
- 🎯 Type Safe: Full TypeScript support
- 📦 Lightweight: Minimal dependencies
Installation
npm install koatty_storeQuick Start
Memory Store
import { CacheStore } from 'koatty_store';
// Create memory store
const store = new CacheStore({
type: 'memory',
keyPrefix: 'myapp:',
maxKeys: 1000
});
// String operations
await store.set('user:1', 'John', 60); // Expires in 60 seconds
const value = await store.get('user:1');
// Hash operations
await store.hset('user:info', 'name', 'John');
await store.hset('user:info', 'age', '25');
const name = await store.hget('user:info', 'name');
// List operations
await store.rpush('queue', 'task1');
await store.rpush('queue', 'task2');
const task = await store.lpop('queue');
// Close store
await store.close();Redis Store
import { CacheStore } from 'koatty_store';
// Create redis store
const store = new CacheStore({
type: 'redis',
host: '127.0.0.1',
port: 6379,
password: 'your-password',
db: 0,
keyPrefix: 'myapp:',
poolSize: 10
});
// Use same API as memory store
await store.set('key', 'value');
await store.close();Configuration
Memory Store Options
interface MemoryStoreOpt {
type: 'memory';
keyPrefix?: string; // Key prefix, default: 'Koatty'
db?: number; // Database index, default: 0
timeout?: number; // Default TTL in seconds, default: 600
maxKeys?: number; // LRU max keys, default: 1000
maxMemory?: number; // Max memory in bytes
evictionPolicy?: 'lru' | 'lfu' | 'random'; // Eviction policy, default: 'lru'
ttlCheckInterval?: number; // TTL check interval in ms, default: 60000
}Redis Store Options
interface RedisStoreOpt {
type: 'redis';
host: string; // Redis host
port: number; // Redis port
password?: string; // Redis password
db?: number; // Database index, default: 0
keyPrefix?: string; // Key prefix, default: 'Koatty'
timeout?: number; // Default TTL in seconds, default: 600
poolSize?: number; // Connection pool size, default: 10
connectTimeout?: number; // Connection timeout in ms, default: 500
}API Reference
String Operations
// Set a string value with optional TTL
await store.set(key: string, value: string | number, timeout?: number): Promise<string>
// Get a string value
await store.get(key: string): Promise<string | null>
// Delete a key
await store.del(key: string): Promise<number>
// Check if key exists
await store.exists(key: string): Promise<number>
// Get TTL of a key
await store.ttl(key: string): Promise<number>
// Set TTL for a key
await store.expire(key: string, timeout: number): Promise<number>
// Increment
await store.incr(key: string): Promise<number>
await store.incrby(key: string, increment: number): Promise<number>
// Decrement
await store.decr(key: string): Promise<number>
await store.decrby(key: string, decrement: number): Promise<number>Hash Operations
// Set hash field with optional TTL (field-level)
await store.hset(name: string, key: string, value: string | number, timeout?: number): Promise<number>
// Get hash field
await store.hget(name: string, key: string): Promise<string | null>
// Delete hash field
await store.hdel(name: string, key: string): Promise<number>
// Check if hash field exists
await store.hexists(name: string, key: string): Promise<number>
// Get all fields and values
await store.hgetall(name: string): Promise<any>
// Get all field names
await store.hkeys(name: string): Promise<string[]>
// Get all values
await store.hvals(name: string): Promise<any[]>
// Get hash length
await store.hlen(name: string): Promise<number>
// Increment hash field
await store.hincrby(name: string, key: string, increment: number): Promise<number>List Operations
// Push to right
await store.rpush(name: string, value: string | number): Promise<number>
// Push to left
await store.lpush(name: string, value: string | number): Promise<number>
// Pop from left
await store.lpop(name: string): Promise<string | null>
// Pop from right
await store.rpop(name: string): Promise<string | null>
// Get list length
await store.llen(name: string): Promise<number>
// Get range
await store.lrange(name: string, start: number, stop: number): Promise<any[]>Set Operations
// Add member with optional TTL
await store.sadd(name: string, value: string | number, timeout?: number): Promise<number>
// Remove member
await store.srem(name: string, key: string): Promise<number>
// Get set size
await store.scard(name: string): Promise<number>
// Check if member exists
await store.sismember(name: string, key: string): Promise<number>
// Get all members
await store.smembers(name: string): Promise<any[]>
// Pop random member
await store.spop(name: string): Promise<any>
// Move member between sets
await store.smove(source: string, destination: string, member: string): Promise<number>Advanced Features
Field-Level TTL for Hash
// Set hash field with TTL (expires in 60 seconds)
await store.hset('user:session', 'token', 'abc123', 60);
// Field will be automatically deleted after TTL expires
setTimeout(async () => {
const token = await store.hget('user:session', 'token');
console.log(token); // null
}, 61000);Singleton Pattern
// Get singleton instance
const store1 = CacheStore.getInstance({
type: 'memory',
keyPrefix: 'app:'
}, 'cache1');
const store2 = CacheStore.getInstance({
type: 'memory',
keyPrefix: 'app:'
}, 'cache1');
console.log(store1 === store2); // true
// Clear specific instance
await CacheStore.clearInstance('cache1');
// Clear all instances
await CacheStore.clearAllInstances();Concurrency Safe Operations
All atomic operations (incr, decr, incrby, decrby, hincrby) are protected with locks to ensure data consistency in concurrent scenarios.
// Safe concurrent increments
await Promise.all([
store.incr('counter'),
store.incr('counter'),
store.incr('counter')
]);
const count = await store.get('counter');
console.log(count); // "3" - guaranteed consistencyBest Practices
1. Use Key Prefix
const store = new CacheStore({
type: 'memory',
keyPrefix: 'myapp:' // Prefix all keys
});2. Set Appropriate TTL
// Short-lived data
await store.set('session:token', token, 3600); // 1 hour
// Long-lived data
await store.set('user:profile', profile, 86400); // 24 hours3. Handle Errors Gracefully
try {
await store.set('key', 'value');
} catch (error) {
console.error('Cache operation failed:', error.message);
// Fallback to database or other source
}4. Close Store on Exit
process.on('SIGINT', async () => {
await store.close();
process.exit(0);
});5. Use Connection Pool for Redis
const store = new CacheStore({
type: 'redis',
host: '127.0.0.1',
port: 6379,
poolSize: 20 // Adjust based on load
});Testing
# Run tests
npm test
# Run tests with coverage
npm test -- --coverage
# Run specific test
npm test -- test/memory.test.tsPerformance
Memory Store
- Operations: ~1,000,000 ops/sec
- LRU Eviction: O(1)
- TTL Check: Background task, configurable interval
Redis Store
- Operations: Depends on Redis server
- Connection Pool: Configurable size
- Auto Reconnect: Exponential backoff strategy
Changelog
See CHANGELOG.md for release history.
License
BSD-3-Clause
Contributing
Contributions are welcome! Please read the contributing guidelines first.
Support
Related Projects
- koatty - The Koatty framework
- koatty_lib - Koatty utilities
- koatty_logger - Koatty logger
