flamecache
v1.0.2
Published
Simple and robust caching layer using Firebase Realtime Database
Downloads
61
Maintainers
Readme
Flamecache
A Redis-like caching layer for Firebase Realtime Database with automatic expiration, counters, and batch operations.
Flamecache is a lightweight, robust caching solution that brings Redis-style operations to Firebase Realtime Database. Perfect for serverless applications, real-time apps, and projects already using Firebase.
✨ Features
- 🚀 Redis-like API - Familiar
get,set,incr,decr,expireoperations - ⚡ Fast & Lightweight - Only ~1KB (minified), no heavy dependencies
- 🔄 Auto-expiration - Built-in TTL support with automatic cleanup
- 📊 Counters - Atomic increment/decrement operations
- 📦 Batch Operations - Multi-get, multi-set, multi-delete
- 🎯 TypeScript - Full type safety with generics
- 🔌 Zero Config - Works out of the box
- 🌐 Serverless Ready - Perfect for Firebase Functions, Vercel, Netlify
📦 Installation
npm install flamecache firebaseFlamecache requires Firebase as a peer dependency. If you don't have Firebase installed:
npm install firebase flamecache🚀 Quick Start
import { createCache } from 'flamecache';
// Initialize cache
const cache = createCache({
firebase: {
apiKey: 'your-api-key',
databaseURL: 'https://your-project.firebaseio.com',
projectId: 'your-project-id',
},
rootPath: 'my-cache', // Root path in Firebase (default: 'flamecache')
ttl: 3600, // Default TTL: 1 hour
disableCache: false, // Optional: disable cache interactions (useful for testing/development)
});
// Basic usage
await cache.set('user:123', { name: 'Alice', email: '[email protected]' });
const user = await cache.get('user:123');
// With custom TTL (in seconds)
await cache.set('temp:token', 'abc123', 300); // Expires in 5 minutes
// Counters
await cache.incr('views:post:456'); // Increment view count
const views = await cache.get('views:post:456'); // 1
// Read-through caching
const posts = await cache.wrap(
'posts:latest',
async () => {
// This function only runs on cache miss
const response = await fetch('https://api.example.com/posts');
return response.json();
},
600
); // Cache for 10 minutes📖 API Reference
Core Operations
get<T>(key: string): Promise<T | null>
Retrieve a value from cache.
const user = await cache.get<User>('user:123');
if (user) {
console.log(user.name);
}set<T>(key: string, data: T, ttl?: number): Promise<void>
Store a value in cache with optional TTL (in seconds).
// With default TTL
await cache.set('user:123', userData);
// With custom TTL (10 minutes)
await cache.set('session:abc', sessionData, 600);
// Never expires
await cache.set('config', configData, 0);del(key: string): Promise<void>
Delete a key from cache.
await cache.del('user:123');has(key: string): Promise<boolean>
Check if a key exists and is not expired.
if (await cache.has('session:abc')) {
console.log('Session is valid');
}clear(): Promise<void>
Clear all cache entries.
await cache.clear();disconnect(): Promise<void>
Close the connection to Firebase. Crucial for Node.js scripts to exit cleanly.
await cache.disconnect();Counter Operations
incr(key: string, by?: number): Promise<number>
Increment a numeric value. Returns the new value.
const views = await cache.incr('views:post:123'); // Increment by 1
const score = await cache.incr('score:player', 10); // Increment by 10decr(key: string, by?: number): Promise<number>
Decrement a numeric value. Returns the new value.
const stock = await cache.decr('stock:item:456'); // Decrement by 1
const credits = await cache.decr('credits:user', 5); // Decrement by 5TTL Operations
expire(key: string, ttl: number): Promise<boolean>
Set or update expiration time for an existing key (in seconds).
await cache.expire('session:abc', 300); // Expire in 5 minutes
await cache.expire('permanent:key', 0); // Remove expirationgetTtl(key: string): Promise<number>
Get remaining time-to-live in seconds.
- Returns
0if key doesn't exist or is expired - Returns
-1if key has no expiration - Returns remaining seconds otherwise
const remaining = await cache.getTtl('session:abc');
if (remaining > 0 && remaining < 300) {
console.log('Session expiring soon!');
}touch(key: string, ttl?: number): Promise<boolean>
Refresh TTL without changing the value.
// Use default TTL
await cache.touch('session:abc');
// Use custom TTL
await cache.touch('session:abc', 3600);Batch Operations
mget<T>(keys: string[]): Promise<(T | null)[]>
Get multiple keys at once.
const [user1, user2, user3] = await cache.mget(['user:1', 'user:2', 'user:3']);mset(entries: Record<string, any>, ttl?: number): Promise<void>
Set multiple keys at once.
await cache.mset(
{
'user:1': userData1,
'user:2': userData2,
'user:3': userData3,
},
600
); // All expire in 10 minutesmdel(keys: string[]): Promise<void>
Delete multiple keys at once.
await cache.mdel(['temp:1', 'temp:2', 'temp:3']);Advanced Operations
wrap<T>(key: string, fetchFn: () => Promise<T>, ttl?: number): Promise<T>
Read-through caching pattern. Gets from cache or fetches and stores automatically.
const userData = await cache.wrap(
'user:123',
async () => {
// This only runs on cache miss
const response = await fetch(`/api/users/123`);
return response.json();
},
300
);pull<T>(key: string): Promise<T | null>
Get and delete in one operation (atomic pop).
const token = await cache.pull('temp:verification-token');
// Token is retrieved and deleted🎯 Use Cases
1. Rate Limiting
async function checkRateLimit(userId: string): Promise<boolean> {
const key = `ratelimit:${userId}`;
const count = await cache.incr(key);
// Set expiration on first request
if (count === 1) {
await cache.expire(key, 60); // Reset every minute
}
return count <= 100; // Max 100 requests per minute
}
// Usage
if (!(await checkRateLimit('user123'))) {
throw new Error('Rate limit exceeded');
}2. Session Management
class SessionManager {
async create(userId: string, data: any): Promise<string> {
const sessionId = generateId();
await cache.set(
`session:${sessionId}`,
{
userId,
...data,
createdAt: Date.now(),
},
3600
); // 1 hour session
return sessionId;
}
async get(sessionId: string) {
return await cache.get(`session:${sessionId}`);
}
async extend(sessionId: string): Promise<boolean> {
return await cache.touch(`session:${sessionId}`, 3600);
}
async destroy(sessionId: string): Promise<void> {
await cache.del(`session:${sessionId}`);
}
}3. API Response Caching
async function getUser(userId: string) {
return cache.wrap(
`api:user:${userId}`,
async () => {
console.log('Fetching from API...');
const response = await fetch(`https://api.example.com/users/${userId}`);
return response.json();
},
600
); // Cache for 10 minutes
}
// First call - fetches from API
const user1 = await getUser('123');
// Second call - uses cache (within 10 minutes)
const user2 = await getUser('123');4. Leaderboard / Scoring
async function addScore(playerId: string, points: number) {
await cache.incr(`score:${playerId}`, points);
}
async function getTopScores(playerIds: string[]) {
const keys = playerIds.map((id) => `score:${id}`);
const scores = await cache.mget<number>(keys);
return playerIds
.map((id, i) => ({
playerId: id,
score: scores[i] || 0,
}))
.sort((a, b) => b.score - a.score);
}5. Temporary Tokens
async function createVerificationToken(email: string): Promise<string> {
const token = generateToken();
await cache.set(`verify:${token}`, { email }, 900); // 15 minutes
return token;
}
async function verifyToken(token: string) {
// Pull removes the token after reading (one-time use)
const data = await cache.pull(`verify:${token}`);
if (!data) {
throw new Error('Invalid or expired token');
}
return data;
}6. Real-time Analytics
// Track page views
async function trackPageView(pageId: string) {
await cache.incr(`views:${pageId}`);
await cache.incr(`views:today:${pageId}`);
// Expire daily stats at midnight
const secondsUntilMidnight = getSecondsUntilMidnight();
await cache.expire(`views:today:${pageId}`, secondsUntilMidnight);
}
// Get analytics
async function getAnalytics(pageIds: string[]) {
const totalKeys = pageIds.map((id) => `views:${id}`);
const todayKeys = pageIds.map((id) => `views:today:${id}`);
const [totalViews, todayViews] = await Promise.all([
cache.mget<number>(totalKeys),
cache.mget<number>(todayKeys),
]);
return pageIds.map((id, i) => ({
pageId: id,
total: totalViews[i] || 0,
today: todayViews[i] || 0,
}));
}📊 Performance
Here are the benchmark results running against a real Firebase Realtime Database instance (50 iterations per operation):
| Operation | Avg Latency | Throughput | | :--------------------- | :--------------- | :---------------- | | GET (Cache Hit) | ~110ms | 9 ops/sec | | SET (Write) | ~120ms | 8 ops/sec | | INCR (Atomic) | ~230ms | 4 ops/sec | | MSET (Batch Write) | ~2.4ms / key | 415+ keys/sec | | MGET (Batch Read) | ~3.6ms / key | 275+ keys/sec |
All benchmarks include real-world network latency to Firebase. Batch operations leverage parallelization and atomic multi-path updates for maximum efficiency.
You can run these benchmarks yourself:
npm run example benchmarkSee the benchmark script for details.
🔧 Configuration
interface CacheConfig {
// Firebase configuration (required)
firebase: {
apiKey: string;
authDomain?: string;
databaseURL: string;
projectId: string;
storageBucket?: string;
messagingSenderId?: string;
appId?: string;
};
// Root path in Firebase database (default: 'flamecache')
rootPath?: string;
// Default TTL in seconds (default: 3600 = 1 hour, 0 = never expires)
ttl?: number;
// Enable debug logs (default: false)
debug?: boolean;
// Disable all cache operations (default: false)
// When true: get/mget return null, set/mset/del do nothing, wrap always fetches
disableCache?: boolean;
}
// Example with all options
const cache = createCache({
firebase: {
/* ... */
},
rootPath: 'my-cache',
ttl: 7200, // 2 hours
debug: true, // Log all operations
});🔑 Key Naming Conventions
Use colons (:) to create hierarchical keys for better organization:
// Good ✅
'user:123';
'user:123:profile';
'session:abc123';
'api:github:users:456';
'ratelimit:user:789';
'cache:posts:latest';
// Avoid ❌
'user_123';
'user-profile-123';
'session.abc123';🎨 TypeScript Support
Full TypeScript support with generics:
interface User {
id: string;
name: string;
email: string;
}
// Type-safe operations
const user = await cache.get<User>('user:123');
// user is typed as User | null
await cache.set<User>('user:123', {
id: '123',
name: 'Alice',
email: '[email protected]',
});
// Works with complex types
type ApiResponse = {
data: User[];
meta: { total: number };
};
const response = await cache.wrap<ApiResponse>('api:users', fetchUsers);🔒 Firebase Security Rules
Development
{
"rules": {
"flamecache": {
".read": true,
".write": true
}
}
}Production
{
"rules": {
"flamecache": {
".read": "auth != null",
".write": "auth != null"
}
}
}Advanced (per-user caching)
{
"rules": {
"flamecache": {
"users": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
}
},
"public": {
".read": true,
".write": "auth != null"
}
}
}
}🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
# Clone the repo
git clone https://github.com/iamanishroy/flamecache.git
cd flamecache
# Install dependencies
npm install
# Run tests
npm test
# Build
npm run build📝 License
MIT © Anish Roy
