@qantesm/nanostorage
v1.0.2
Published
High-performance LocalStorage compression using native CompressionStream API. Zero-dependency, < 1KB, non-blocking.
Maintainers
Readme
🗜️ NanoStorage
High-performance LocalStorage compression using native CompressionStream API.
Store up to 10x more data in LocalStorage with browser-native GZIP compression. Zero dependencies, under 1KB, non-blocking.
✨ Features
| Feature | Description |
|---------|-------------|
| 🚀 Native Speed | Uses browser's C++ compression engine, not JavaScript |
| 📦 < 1KB | Minimal footprint, zero dependencies |
| ⚡ Non-Blocking | Stream-based async API prevents UI freezing |
| 🔧 Smart Threshold | Auto-skips compression for small data |
| 📝 TypeScript | Full type definitions included |
| 🎯 Simple API | Just setItem and getItem |
📊 Performance Comparison
Benchmark Results (5 MB JSON, Chrome)
| Metric | NanoStorage | lz-string | Winner | |--------|-------------|-----------|--------| | Compress Time | 95 ms | 1.3 s | 🏆 NanoStorage (14x) | | Decompress Time | 57 ms | 67 ms | 🏆 NanoStorage | | Compressed Size | 70 KB | 168 KB | 🏆 NanoStorage (2.4x) | | Compression Ratio | 98.6% | 96.6% | 🏆 NanoStorage |
💡 4/4 categories won! 5 MB JSON → 70 KB in 95ms with faster decompression.
Why So Fast?
| Feature | lz-string | NanoStorage | |---------|-----------|-------------| | Engine | JavaScript (Main Thread) | C++ (Browser Native) | | UI Blocking | ❌ Yes, freezes on big data | ✅ No, async streams | | Bundle Size | ~18 KB | < 1 KB | | Algorithm | LZW (1984) | GZIP/Deflate (Industry Standard) |
Real-World Example
📁 Original: 1 MB JSON
↓ GZIP: ~100 KB
↓ Base64: ~133 KB
💾 Final: 133 KB (87% savings!)📦 Installation
npm install @qantesm/nanostorageyarn add @qantesm/nanostoragepnpm add @qantesm/nanostorage🚀 Quick Start
import { nanoStorage } from '@qantesm/nanostorage';
// Store data (automatically compressed)
await nanoStorage.setItem('user', {
name: 'Muhammet',
preferences: { theme: 'dark', language: 'tr' },
history: [...largeArray]
});
// Retrieve data (automatically decompressed)
const user = await nanoStorage.getItem('user');
console.log(user.name); // 'Muhammet'📖 API Reference
Default Instance
import { nanoStorage } from '@qantesm/nanostorage';A pre-configured instance ready to use.
Create Custom Instance
import { createStorage } from '@qantesm/nanostorage';
const storage = createStorage({
threshold: 500, // Bytes. Skip compression for smaller data
algorithm: 'gzip', // 'gzip' or 'deflate'
keyPrefix: 'myapp:', // Prefix for all keys
});Methods
setItem<T>(key: string, value: T): Promise<void>
Store any JSON-serializable value with automatic compression.
await storage.setItem('settings', { theme: 'dark' });
await storage.setItem('items', [1, 2, 3, 4, 5]);
await storage.setItem('count', 42);getItem<T>(key: string): Promise<T | null>
Retrieve and decompress a stored value.
const settings = await storage.getItem<Settings>('settings');
if (settings) {
console.log(settings.theme);
}removeItem(key: string): Promise<void>
Remove an item from storage.
await storage.removeItem('settings');hasItem(key: string): Promise<boolean>
Check if a key exists.
if (await storage.hasItem('user')) {
// User data exists
}keys(): Promise<string[]>
Get all stored keys.
const allKeys = await storage.keys();
// ['user', 'settings', 'cache']clear(): Promise<void>
Remove all items managed by this instance.
await storage.clear();getStats(): Promise<StorageStats>
Get compression statistics.
const stats = await storage.getStats();
console.log(`Compression ratio: ${(1 - stats.compressionRatio) * 100}%`);
// "Compression ratio: 85%"Low-Level Functions
For advanced use cases, you can use the compression functions directly:
import { compress, decompress, isSupported } from '@qantesm/nanostorage';
// Check browser support
if (!isSupported()) {
console.warn('CompressionStream not available');
}
// Direct compression
const result = await compress({ data: 'large payload' });
console.log(result.data); // Compressed string
console.log(result.originalSize); // Original byte size
console.log(result.compressedSize); // Compressed byte size
console.log(result.wasCompressed); // true if compression was applied
// Direct decompression
const original = await decompress(result.data);🔧 Configuration Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| threshold | number | 500 | Minimum bytes to trigger compression. Smaller data is stored raw. |
| algorithm | 'gzip' \| 'deflate' | 'gzip' | Compression algorithm to use. |
| keyPrefix | string | 'ns:' | Prefix added to all storage keys. |
Why Threshold?
GZIP adds ~18 bytes of header overhead. For tiny data like { theme: 'dark' }, compression would actually increase size. The threshold ensures only beneficial compressions occur.
🌐 Browser Support
| Browser | Version | Status | |---------|---------|--------| | Chrome | 80+ | ✅ Supported | | Edge | 80+ | ✅ Supported | | Firefox | 113+ | ✅ Supported | | Safari | 16.4+ | ✅ Supported | | Opera | 67+ | ✅ Supported | | IE | All | ❌ Not Supported |
💡 Use Cases
🎮 Game Save Data
await nanoStorage.setItem('gameState', {
level: 42,
inventory: [...hundredsOfItems],
achievements: [...],
settings: {...}
});📝 Form Draft Auto-Save
// Save draft as user types
await nanoStorage.setItem('formDraft', formData);
// Restore on page reload
const draft = await nanoStorage.getItem('formDraft');
if (draft) {
restoreForm(draft);
}🛒 E-Commerce Cart
await nanoStorage.setItem('cart', {
items: cartItems,
lastUpdated: Date.now()
});📊 Dashboard State (Redux/Vuex)
// Persist state
store.subscribe(() => {
nanoStorage.setItem('appState', store.getState());
});
// Hydrate on load
const savedState = await nanoStorage.getItem('appState');
if (savedState) {
store.dispatch({ type: 'HYDRATE', payload: savedState });
}⚠️ Important Notes
Async API
Unlike native localStorage.getItem() which is synchronous, NanoStorage uses Promises:
// ❌ Won't work
const data = nanoStorage.getItem('key');
// ✅ Correct
const data = await nanoStorage.getItem('key');This is intentional - async operations prevent UI blocking during compression.
Data Must Be JSON-Serializable
// ✅ These work
await storage.setItem('obj', { a: 1 });
await storage.setItem('arr', [1, 2, 3]);
await storage.setItem('str', 'hello');
await storage.setItem('num', 42);
await storage.setItem('bool', true);
await storage.setItem('null', null);
// ❌ These won't work
await storage.setItem('fn', () => {}); // Functions
await storage.setItem('date', new Date()); // Dates (use .toISOString())
await storage.setItem('map', new Map()); // Map/Set (convert to array)🔬 Technical Details
Compression Pipeline
┌─────────────┐ ┌──────────────┐ ┌─────────────────┐ ┌─────────┐
│ JavaScript │ ──► │ TextEncoder │ ──► │ CompressionStream│ ──► │ Base64 │
│ Object │ │ (UTF-8) │ │ (Native GZIP) │ │ String │
└─────────────┘ └──────────────┘ └─────────────────┘ └─────────┘Decompression Pipeline
┌─────────┐ ┌───────────────────┐ ┌──────────────┐ ┌─────────────┐
│ Base64 │ ──► │ DecompressionStream│ ──► │ TextDecoder │ ──► │ JavaScript │
│ String │ │ (Native GZIP) │ │ (UTF-8) │ │ Object │
└─────────┘ └───────────────────┘ └──────────────┘ └─────────────┘Storage Format
Compressed data is prefixed with a marker byte:
R- Raw (uncompressed) dataG- GZIP compressedD- Deflate compressed
📄 License
MIT © Muhammet Ali Büyük
🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing) - Open a Pull Request
