Storage Adapter
Pluggable storage interface for BigBlocks authentication and data persistence
A pluggable storage interface that provides a consistent API for data persistence across different environments. BigBlocks uses storage adapters for authentication data, session management, and backup persistence.
interface StorageAdapter { get: (key: string) => Promise<string | null>; set: (key: string, value: string) => Promise<void>; delete: (key: string) => Promise<void>; clear?: () => Promise<void>; }
Installation
npx bigblocks add storage-adapter
Import
import { createMemoryStorage, createLocalStorage } from 'bigblocks';
Interface
All storage adapters implement the StorageAdapter
interface:
interface StorageAdapter {
get: (key: string) => Promise<string | null>;
set: (key: string, value: string) => Promise<void>;
delete: (key: string) => Promise<void>;
clear?: () => Promise<void>;
}
Built-in Adapters
Memory Storage
For testing and development without persistence:
import { createMemoryStorage } from 'bigblocks';
const storage = createMemoryStorage();
// Usage with auth
const authManager = createAuthManager({
apiUrl: '/api',
storage: storage
});
Local Storage
For browser-based persistence:
import { createLocalStorage } from 'bigblocks';
const storage = createLocalStorage();
// With namespace
const namespacedStorage = createLocalStorage('myapp');
Session Storage
For temporary session-based storage:
import { createSessionStorage } from 'bigblocks';
const storage = createSessionStorage();
Custom Storage Adapters
Server-Side Storage
For Node.js environments:
import { StorageAdapter } from 'bigblocks';
class ServerStorageAdapter implements StorageAdapter {
private storage = new Map<string, string>();
async get(key: string): Promise<string | null> {
// Implement database/file system retrieval
return this.storage.get(key) || null;
}
async set(key: string, value: string): Promise<void> {
// Implement database/file system storage
this.storage.set(key, value);
}
async delete(key: string): Promise<void> {
// Implement database/file system removal
this.storage.delete(key);
}
async clear(): Promise<void> {
// Clear all stored data
this.storage.clear();
}
}
const auth = createAuthManager({
apiUrl: '/api',
storage: new ServerStorageAdapter()
});
Redis Storage
For distributed applications:
import Redis from 'ioredis';
import { StorageAdapter } from 'bigblocks';
class RedisStorageAdapter implements StorageAdapter {
private redis: Redis;
private namespace: string;
constructor(redisUrl: string, namespace = 'bigblocks:') {
this.redis = new Redis(redisUrl);
this.namespace = namespace;
}
async get(key: string): Promise<string | null> {
return this.redis.get(this.namespace + key);
}
async set(key: string, value: string): Promise<void> {
await this.redis.set(this.namespace + key, value);
}
async delete(key: string): Promise<void> {
await this.redis.del(this.namespace + key);
}
async clear(): Promise<void> {
const keys = await this.redis.keys(this.namespace + '*');
if (keys.length > 0) {
await this.redis.del(...keys);
}
}
}
const auth = createBigBlocksAuth({
storage: new RedisStorageAdapter(process.env.REDIS_URL!)
});
Encrypted Storage
For enhanced security:
import { StorageAdapter } from 'bigblocks';
import CryptoJS from 'crypto-js';
class EncryptedStorageAdapter implements StorageAdapter {
private storage: StorageAdapter;
private encryptionKey: string;
constructor(storage: StorageAdapter, encryptionKey: string) {
this.storage = storage;
this.encryptionKey = encryptionKey;
}
async get(key: string): Promise<string | null> {
const encrypted = await this.storage.get(key);
if (!encrypted) return null;
try {
const bytes = CryptoJS.AES.decrypt(encrypted, this.encryptionKey);
return bytes.toString(CryptoJS.enc.Utf8);
} catch {
return null; // Invalid or corrupted data
}
}
async set(key: string, value: string): Promise<void> {
const encrypted = CryptoJS.AES.encrypt(value, this.encryptionKey).toString();
await this.storage.set(key, encrypted);
}
async delete(key: string): Promise<void> {
await this.storage.delete(key);
}
async clear(): Promise<void> {
await this.storage.clear();
}
}
// Usage
const baseStorage = createLocalStorage();
const encryptedStorage = new EncryptedStorageAdapter(baseStorage, 'secret-key');
const auth = createAuthManager({
apiUrl: '/api',
storage: encryptedStorage
});
Usage with Components
AuthManager
import { createAuthManager, createMemoryStorage } from 'bigblocks';
const authManager = createAuthManager({
apiUrl: '/api',
storage: createMemoryStorage(), // For testing
storageNamespace: 'auth:'
});
BitcoinAuthProvider
import { BitcoinAuthProvider, createLocalStorage } from 'bigblocks';
function App() {
return (
<BitcoinAuthProvider
appId="my-app"
apiUrl="/api"
storage={createLocalStorage('my-app')}
>
{/* Your app */}
</BitcoinAuthProvider>
);
}
createBigBlocksAuth
import { createBigBlocksAuth, createSessionStorage } from 'bigblocks';
const auth = createBigBlocksAuth({
apiUrl: '/api',
storage: createSessionStorage(),
storageNamespace: 'bigblocks:auth:'
});
Configuration Options
Storage Namespacing
Use namespaces to avoid key conflicts:
const auth = createAuthManager({
apiUrl: '/api',
storage: createLocalStorage(),
storageNamespace: 'myapp:auth:' // Prefix for all keys
});
Multiple Storage Instances
Use different storage for different purposes:
const sessionStorage = createSessionStorage();
const localStorage = createLocalStorage();
// Session data
const auth = createAuthManager({
apiUrl: '/api',
storage: sessionStorage
});
// Persistent user preferences
const preferences = {
storage: localStorage,
get: (key: string) => localStorage.get(`prefs:${key}`),
set: (key: string, value: string) => localStorage.set(`prefs:${key}`, value)
};
Testing
Mock Storage
For unit tests:
import { StorageAdapter } from 'bigblocks';
class MockStorageAdapter implements StorageAdapter {
private data: Record<string, string> = {};
async get(key: string): Promise<string | null> {
return this.data[key] || null;
}
async set(key: string, value: string): Promise<void> {
this.data[key] = value;
}
async delete(key: string): Promise<void> {
delete this.data[key];
}
async clear(): Promise<void> {
this.data = {};
}
// Test helpers
getData() {
return { ...this.data };
}
setData(data: Record<string, string>) {
this.data = { ...data };
}
}
// In tests
const mockStorage = new MockStorageAdapter();
const auth = createAuthManager({
apiUrl: '/api',
storage: mockStorage
});
// Verify storage calls
expect(mockStorage.getData()).toEqual({
'auth:session': 'encrypted-session-data'
});
Error Handling
Graceful Degradation
class FallbackStorageAdapter implements StorageAdapter {
private primary: StorageAdapter;
private fallback: StorageAdapter;
constructor(primary: StorageAdapter, fallback: StorageAdapter) {
this.primary = primary;
this.fallback = fallback;
}
async get(key: string): Promise<string | null> {
try {
return await this.primary.get(key);
} catch {
return await this.fallback.get(key);
}
}
async set(key: string, value: string): Promise<void> {
try {
await this.primary.set(key, value);
} catch {
await this.fallback.set(key, value);
}
}
async delete(key: string): Promise<void> {
await Promise.all([
this.primary.delete(key).catch(() => {}),
this.fallback.delete(key).catch(() => {})
]);
}
async clear(): Promise<void> {
await Promise.all([
this.primary.clear().catch(() => {}),
this.fallback.clear().catch(() => {})
]);
}
}
// Usage
const storage = new FallbackStorageAdapter(
createLocalStorage(),
createMemoryStorage()
);
Storage Quota Management
class QuotaAwareStorage implements StorageAdapter {
private storage: StorageAdapter;
private maxSize: number;
constructor(storage: StorageAdapter, maxSize = 5 * 1024 * 1024) { // 5MB
this.storage = storage;
this.maxSize = maxSize;
}
async setItem(key: string, value: string): Promise<void> {
if (value.length > this.maxSize) {
throw new Error('Value exceeds storage quota');
}
try {
await this.storage.set(key, value);
} catch (error) {
if (error.name === 'QuotaExceededError') {
// Clear old data and retry
await this.cleanupOldData();
await this.storage.set(key, value);
} else {
throw error;
}
}
}
private async cleanupOldData(): Promise<void> {
// Implement cleanup strategy
console.warn('Storage quota exceeded, cleaning up old data');
}
async get(key: string): Promise<string | null> {
return this.storage.get(key);
}
async delete(key: string): Promise<void> {
return this.storage.delete(key);
}
async clear(): Promise<void> {
return this.storage.clear();
}
}
Best Practices
- Choose the Right Adapter: Use memory storage for testing, local storage for browsers, custom adapters for servers
- Handle Async Operations: Many storage operations are async, always await them
- Use Namespaces: Prevent key conflicts with proper namespacing
- Error Handling: Implement fallback strategies for storage failures
- Security: Use encrypted storage for sensitive data
- Testing: Mock storage for unit tests to avoid side effects
Common Patterns
Configuration Factory
function createStorageConfig(env: 'development' | 'test' | 'production') {
switch (env) {
case 'test':
return createMemoryStorage();
case 'development':
return createLocalStorage('dev');
case 'production':
return new EncryptedStorageAdapter(
createLocalStorage(),
process.env.ENCRYPTION_KEY!
);
}
}
const storage = createStorageConfig(process.env.NODE_ENV);
Multi-Layer Storage
class CachedStorageAdapter implements StorageAdapter {
private cache: StorageAdapter;
private persistent: StorageAdapter;
constructor(cache: StorageAdapter, persistent: StorageAdapter) {
this.cache = cache;
this.persistent = persistent;
}
async get(key: string): Promise<string | null> {
// Try cache first
let value = await this.cache.get(key);
if (value === null) {
// Fallback to persistent storage
value = await this.persistent.get(key);
if (value !== null) {
// Cache for next time
await this.cache.set(key, value);
}
}
return value;
}
async set(key: string, value: string): Promise<void> {
await Promise.all([
this.cache.set(key, value),
this.persistent.set(key, value)
]);
}
async delete(key: string): Promise<void> {
await Promise.all([
this.cache.delete(key),
this.persistent.delete(key)
]);
}
async clear(): Promise<void> {
await Promise.all([
this.cache.clear(),
this.persistent.clear()
]);
}
}
Related Components
- createMemoryStorage - Memory-based storage utility
- createLocalStorage - Browser localStorage wrapper
- AuthManager - Uses storage for session persistence
- BitcoinAuthProvider - Provider with storage configuration
API Reference
This component provides a consistent storage interface for all BigBlocks components and utilities. The interface supports both synchronous and asynchronous operations to accommodate different storage backends.
Core Interface
interface StorageAdapter {
get: (key: string) => Promise<string | null>;
set: (key: string, value: string) => Promise<void>;
delete: (key: string) => Promise<void>;
clear?: () => Promise<void>;
}
Built-in Utilities
createMemoryStorage()
- In-memory storage for testingcreateLocalStorage(namespace?)
- Browser localStorage wrappercreateSessionStorage(namespace?)
- Browser sessionStorage wrapper