One unified, secure, and modern storage solution for React applications.
React Unified Storage is a comprehensive, type-safe storage library that provides a modern API for managing application data with support for multiple storage backends, encryption, compression, and real-time cross-tab synchronization.
- π TypeScript-first with strict typing and generics
- π End-to-end encryption with AES-GCM and PBKDF2
- π¦ Automatic compression with gzip
- π Cross-tab synchronization via BroadcastChannel API
- πΎ Multiple storage drivers (IndexedDB, localStorage, sessionStorage, memory)
- β‘ React hooks with automatic re-rendering
- π‘οΈ SSR-safe with automatic fallback to in-memory storage
- π Schema validation with Zod integration
- β° TTL support for automatic data expiration
- π Versioning & migrations for data evolution
- π Metadata tracking (timestamps, driver info, versions)
- π― Tiny bundle (<6KB gzipped core, <10KB React package)
- π§ͺ Comprehensive testing with Vitest
- π Full TypeScript support with generated .d.ts files
# React package (includes everything)
pnpm add @sitharaj08/react-unified-storage
# Core package only (framework-agnostic)
pnpm add @sitharaj08/react-unified-storage-core
# Peer dependencies (if using core package)
pnpm add zod
import { StorageProvider, useStore } from '@sitharaj08/react-unified-storage';
function App() {
return (
<StorageProvider config={{ driver: 'auto' }}>
<UserProfile />
</StorageProvider>
);
}
function UserProfile() {
const [user, setUser] = useStore('user', {
defaultValue: { name: 'Anonymous', theme: 'light' }
});
return (
<div>
<h1>Welcome, {user.name}!</h1>
<button onClick={() => setUser({ ...user, theme: 'dark' })}>
Toggle Theme
</button>
</div>
);
}
import { setup, read, write } from '@sitharaj08/react-unified-storage-core';
// Initialize storage
setup({
driver: 'auto',
encryption: { key: 'your-secret-key' },
compression: true
});
// Store and retrieve data
await write('settings', { theme: 'dark', language: 'en' });
const settings = await read('settings');
@sitharaj08/react-unified-storage
- React bindings with hooks and context provider@sitharaj08/react-unified-storage-core
- Core storage functionality (framework-agnostic)
Driver | Description | SSR Safe | Persistence | Size Limit | Best For |
---|---|---|---|---|---|
auto |
Auto-selects best available | β | β | Varies | General use |
idb |
IndexedDB | β | β | ~50MB+ | Large datasets |
local |
localStorage | β | β | ~5-10MB | Small data |
session |
sessionStorage | β | Tab-only | ~5-10MB | Temporary data |
memory |
In-memory | β | Tab-only | Unlimited | SSR/Caching |
const [value, setValue, meta] = useStore<T>(key, options?);
Parameters:
key: string
- Storage keyoptions?: UseStoreOptions<T>
Returns:
value: T
- Current stored valuesetValue: (value: T) => Promise<void>
- Update functionmeta?: StorageMeta
- Metadata (whenmetadata: true
)
Options:
interface UseStoreOptions<T> {
defaultValue?: T;
schema?: z.ZodSchema<T>;
version?: number;
metadata?: boolean;
ttlMs?: number;
}
Suspense-enabled version of useStore
:
const [value, setValue] = useStoreSuspense<T>(key, options);
const collection = useCollection<T>(name, options?);
Returns a collection instance with CRUD operations:
await collection.add({ id: 1, name: 'Item' });
const items = await collection.list();
await collection.update(1, { name: 'Updated' });
await collection.remove(1);
setup(config: StorageConfig): Promise<void>
Configuration Options:
interface StorageConfig {
driver?: StorageDriver;
namespace?: string;
broadcast?: boolean;
encryption?: {
key: string;
salt?: string;
};
compression?: boolean;
errorHandler?: (error: Error) => void;
}
// Basic operations
read<T>(key: string): Promise<T | null>
write<T>(key: string, value: T, options?: WriteOptions): Promise<void>
remove(key: string): Promise<void>
// Bulk operations
keys(prefix?: string): Promise<string[]>
clearAll(): Promise<void>
// Collections
createCollection<T>(name: string): Collection<T>
// Utilities
driverId(): string
getCurrentDriver(): StorageDriver
subscribe(callback: BroadcastCallback): () => void
interface Collection<T extends { id: string | number }> {
add(item: Omit<T, 'id'>): Promise<T>;
get(id: string | number): Promise<T | null>;
update(id: string | number, updates: Partial<T>): Promise<T>;
remove(id: string | number): Promise<void>;
list(filter?: (item: T) => boolean): Promise<T[]>;
clear(): Promise<void>;
}
<StorageProvider config={{
encryption: {
key: 'your-32-character-secret-key-here!',
salt: 'optional-salt-for-key-derivation'
}
}}>
<App />
</StorageProvider>
Security Notes:
- Uses PBKDF2 for key derivation (100,000 iterations)
- AES-GCM for encryption with random IVs
- Keys are derived per storage operation for security
import { z } from 'zod';
const userSchemaV1 = z.object({
name: z.string(),
email: z.string()
});
const userSchemaV2 = z.object({
name: z.string(),
email: z.string(),
avatar: z.string().optional()
});
const [user, setUser] = useStore('user', {
schema: userSchemaV2,
version: 2,
// Migration function (if needed)
migrate: (oldData, oldVersion) => {
if (oldVersion === 1) {
return { ...oldData, avatar: undefined };
}
return oldData;
}
});
// Automatic synchronization (enabled by default with broadcast: true)
<StorageProvider config={{ broadcast: true }}>
<App />
</StorageProvider>
// Manual subscription (core API)
import { subscribe } from '@sitharaj08/react-unified-storage-core';
const unsubscribe = subscribe((key, envelope) => {
console.log(`Key ${key} changed:`, envelope?.data);
});
// Expires in 1 hour
await write('temp-token', token, { ttlMs: 60 * 60 * 1000 });
// Check expiration
const [data, meta] = await readWithMeta('temp-token');
if (meta?.expiresAt && Date.now() > meta.expiresAt) {
console.log('Token expired');
}
<StorageProvider config={{
errorHandler: (error) => {
console.error('Storage error:', error);
// Send to error reporting service
reportError(error);
}
}}>
<App />
</StorageProvider>
The library automatically detects SSR environments and falls back to memory storage:
// This works in both SSR and client environments
<StorageProvider config={{ driver: 'auto' }}>
<App />
</StorageProvider>
SSR Behavior:
- Server: Uses memory storage (no persistence)
- Client: Hydrates to chosen driver (IndexedDB/localStorage)
- Data consistency: Server and client data are isolated
import { setup, read, write } from '@sitharaj08/react-unified-storage-core';
import { MemoryDriver } from '@sitharaj08/react-unified-storage-core';
// Use memory driver for testing
setup({ driver: 'memory' });
// Your tests here
test('should store and retrieve data', async () => {
await write('test', { value: 42 });
const result = await read('test');
expect(result).toEqual({ value: 42 });
});
- Bundle Size: Core: <6KB, React: <10KB (gzipped)
- Operations: ~1-2ms for localStorage, ~5-10ms for IndexedDB
- Memory Usage: Minimal overhead, efficient data structures
- Compression: Up to 70% size reduction for text data
"Storage not initialized"
// Ensure StorageProvider wraps your components
<StorageProvider config={{ driver: 'auto' }}>
<App />
</StorageProvider>
"Broadcasting not enabled"
// Enable broadcasting in config
<StorageProvider config={{ broadcast: true }}>
<App />
</StorageProvider>
SSR hydration mismatch
// Use memory driver for SSR-critical data
<StorageProvider config={{ driver: 'memory' }}>
<App />
</StorageProvider>
We welcome contributions! Please see our Contributing Guide for details.
# Clone the repository
git clone https://github.com/sitharaj08/react-unified-storage.git
cd react-unified-storage
# Install dependencies
pnpm install
# Start development
pnpm dev
# Run tests
pnpm test
# Build packages
pnpm build
- Zod for schema validation
- fflate for compression
- BroadcastChannel API for cross-tab sync
- RN/Expo AsyncStorage driver
- DevTools overlay
- Collection API for IndexedDB
- React Query integration
## π License
Copyright 2025 Sitharaj Seenivasan
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.