How the Storage Abstraction Layer in LunaTV Works: IStorage Interface and Multi-Backend Architecture
LunaTV implements a typed IStorage interface that abstracts play records, user accounts, and configuration data across four backends—LocalStorage, Redis, Upstash, and Kvrocks—selected at runtime via the NEXT_PUBLIC_STORAGE_TYPE environment variable.
The storage abstraction layer in LunaTV provides a unified, type-safe approach to persistent data management across diverse deployment environments. By declaring all data operations through a single IStorage contract defined in src/lib/types.ts, the application decouples business logic from storage mechanics, allowing seamless switching between browser-based localStorage for personal use and distributed Redis clusters for production workloads.
Core Architecture of the LunaTV Storage Abstraction Layer
The IStorage Interface Contract
All persistent data—including play records, favorites, user accounts, search history, admin configuration, and skip-config settings—flows through the IStorage interface declared in src/lib/types.ts. This contract defines async methods for every data operation, ensuring type safety across the entire codebase. The interface also optionally exposes migrateData and migratePasswords hooks for structural upgrades and security hardening.
Factory Pattern and Environment Selection
The runtime selection logic resides in src/lib/db.ts within the getStorage factory function. This module reads the NEXT_PUBLIC_STORAGE_TYPE environment variable (defaulting to localstorage) and instantiates the appropriate concrete class. The function returns a singleton instance cached in storageInstance, ensuring consistent state across API routes while preventing redundant connection overhead.
Storage Implementations and Backend Options
LocalStorage for Client-Side Deployments
When NEXT_PUBLIC_STORAGE_TYPE is unset or explicitly set to localstorage, the factory returns the LocalStorage class. This implementation persists data using the browser's native localStorage API, making it ideal for single-user deployments or offline-first scenarios where no server-side infrastructure is required.
Redis, Upstash, and Kvrocks for Production
For server-side or multi-user deployments, LunaTV supports three Redis-compatible backends:
- RedisStorage: Connects to self-hosted Redis instances using the
REDIS_URLenvironment variable. - UpstashRedisStorage: Targets cloud-hosted Upstash Redis, authenticated via
UPSTASH_URLandUPSTASH_TOKEN. - KvrocksStorage: Interfaces with Kvrocks servers for high-throughput scenarios requiring persistent Redis semantics.
The BaseRedisStorage Base Class
All Redis-compatible implementations extend BaseRedisStorage defined in src/lib/redis-base.db.ts. This abstract class implements the full IStorage contract using generic Redis commands—hashes for key-value pairs, sets for collections, and lists for sequential data. Concrete subclasses only supply connection details (client instance and service name), eliminating duplicate logic while enforcing consistent data structures across Redis variants.
Data Organization and Key Naming Conventions
Namespaced User Data Layout
The storage layer organizes user-specific data using strict key prefixes to prevent collisions and enable efficient querying. In Redis-backed implementations, keys follow the pattern u:<user>:<type>:
u:<user>:pr— Hash containing play records indexed by content ID.u:<user>:fav— Hash of user favorites with metadata.u:<user>:pwd— String storing the bcrypt-hashed password.u:<user>:sh— List of recent search keywords, trimmed to the last N entries.u:<user>:skip— Hash of skip-configuration objects (intro/outro timestamps).
O(1) Operations and Bulk Deletions
The Redis data structures enable constant-time read and write operations. Bulk deletions—such as deleteAllPlayRecords—execute as single DEL commands on the respective hash keys, ensuring efficient cleanup without iterating through individual records.
Migration Utilities and Data Maintenance
The IStorage interface optionally includes migration helpers that facilitate schema evolution. Both UpstashRedisStorage and RedisStorage provide implementations of migrateData that convert legacy flat-key structures into the current hash-based format. The migratePasswords method upgrades clear-text or weakly hashed credentials to salted bcrypt hashes, enforcing modern security standards without manual database intervention.
Implementing the Storage Layer in Practice
Initializing the Storage Singleton
Application code never instantiates storage classes directly. Instead, they import the DB wrapper from src/lib/db.ts, which lazily initializes the singleton through getStorage():
// src/lib/db.ts (simplified)
function createStorage(): IStorage {
const type = process.env.NEXT_PUBLIC_STORAGE_TYPE || 'localstorage';
switch (type) {
case 'redis':
return new RedisStorage();
case 'upstash':
return new UpstashRedisStorage();
case 'kvrocks':
return new KvrocksStorage();
default:
return new LocalStorage();
}
}
Consuming the Abstraction in API Routes
API endpoints in src/app/api/* interact with storage through the db instance without knowing the underlying backend. The following login route demonstrates this decoupling:
// src/app/api/login/route.ts
import { getDB } from '@/lib/db';
const db = getDB();
export async function POST(req: Request) {
const { username, password } = await req.json();
const ok = await db.verifyUser(username, password);
return ok
? NextResponse.json({ success: true })
: NextResponse.json({ error: 'Invalid credentials' }, { status: 401 });
}
Example of persisting playback state:
await db.setPlayRecord('alice', 'movie:123', {
title: 'Inception',
source_name: 'netflix',
cover: '/covers/inception.jpg',
year: '2010',
index: 1,
total_episodes: 1,
play_time: 120,
total_time: 8880,
save_time: Date.now(),
search_title: 'Inception'
});
Summary
- LunaTV's storage abstraction layer centers on the
IStorageinterface insrc/lib/types.ts, defining contracts for all data operations. - Four storage backends—LocalStorage, Redis, Upstash, and Kvrocks—are selectable via
NEXT_PUBLIC_STORAGE_TYPEwithout modifying business logic. - BaseRedisStorage in
src/lib/redis-base.db.tsprovides shared Redis implementations, while subclasses handle connection specifics. - Namespaced key patterns like
u:<user>:prandu:<user>:favensure O(1) access patterns and efficient bulk operations. - Migration helpers enable zero-downtime schema upgrades and password hashing improvements across compatible backends.
Frequently Asked Questions
What is the default storage backend in LunaTV?
If the NEXT_PUBLIC_STORAGE_TYPE environment variable is undefined, the system defaults to localstorage, returning a LocalStorage instance suitable for browser-only deployments. This ensures the application runs immediately after cloning without requiring external database infrastructure.
How does LunaTV handle data migration between storage versions?
The IStorage interface declares optional migrateData and migratePasswords methods. Redis-backed implementations traverse legacy flat keys and transform them into the current hash and list structures, while simultaneously upgrading password storage to bcrypt. These utilities run as one-off administrative tasks invoked through API endpoints or CLI scripts.
Can I switch from LocalStorage to Redis without changing application code?
Yes. Changing NEXT_PUBLIC_STORAGE_TYPE from localstorage to redis, upstash, or kvrocks in your environment configuration suffices. The getStorage factory in src/lib/db.ts instantiates the appropriate class, while the DB wrapper ensures all API routes continue functioning identically regardless of the underlying persistence mechanism.
Where is the storage type configured in LunaTV?
Configuration occurs entirely through the NEXT_PUBLIC_STORAGE_TYPE environment variable. Supported values are localstorage, redis, upstash, and kvrocks. For Redis variants, additional variables like REDIS_URL or UPSTASH_TOKEN must be set to establish connections, but no TypeScript code changes are required to swap backends.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →