@inputless/storage
v1.0.6
Published
Data persistence and storage management for behavioral events
Maintainers
Readme
@inputless/storage
Storage management system for behavioral events with multi-backend support, automatic cleanup, and quota management.
Purpose
Manages data persistence, retrieval, and cleanup for behavioral analytics data in the browser. Provides unified API across localStorage, IndexedDB, and SessionStorage with automatic retention policies, compression, and quota management.
Features
- Multiple Storage Backends: localStorage, IndexedDB, SessionStorage with adapter pattern
- Automatic Cleanup: Configurable retention policies with automatic expiration
- Batch Operations: Efficient batch read/write operations
- Data Compression: Optional compression for space efficiency (gzip/brotli)
- Storage Quota Management: Graceful handling of storage limits with automatic space freeing
- Type Safety: Full TypeScript type safety with
@inputless/events - Performance: Optimized for fast operations (<10ms localStorage, <50ms IndexedDB)
Installation
npm install @inputless/storageDependencies
@inputless/events- Type definitions for behavioral events
Quick Start (Local Only)
import { StorageManager, LocalStorageAdapter } from '@inputless/storage';
import type { BaseEvent } from '@inputless/events';
import { generateEventId } from '@inputless/events';
// Initialize storage manager (local only - no API endpoints)
const storage = new StorageManager({
adapter: new LocalStorageAdapter(),
maxSize: 10000,
retentionDays: 30,
compression: false,
debug: true,
});
// Store events
const events: BaseEvent[] = [
{
id: generateEventId(),
type: 'ui.click',
timestamp: Date.now(),
sessionId: 'session-123',
// ... other event properties
},
];
await storage.storeEvents(events);
// Retrieve events by session
const sessionEvents = await storage.getEvents('session-123');
// Get storage statistics
const stats = await storage.getStatistics();
console.log(`Total events: ${stats.totalEvents}`);
console.log(`Storage usage: ${stats.totalSize} bytes`);
console.log(`Events by session:`, stats.eventsBySession);
console.log(`Events by type:`, stats.eventsByType);Usage Examples
Example 1: Basic Setup with LocalStorage (Local Only)
import { StorageManager, LocalStorageAdapter } from '@inputless/storage';
import type { BaseEvent } from '@inputless/events';
// Initialize storage manager with localStorage (local only)
const storage = new StorageManager({
adapter: new LocalStorageAdapter({ prefix: 'myapp_' }),
maxSize: 5000,
retentionDays: 7,
batchSize: 100,
keyPrefix: 'myapp_',
debug: true,
});
// Store single event
const event: BaseEvent = {
id: 'event-1',
type: 'ui.click',
timestamp: Date.now(),
sessionId: 'session-123',
};
await storage.storeEvent(event);
// Store multiple events
const events: BaseEvent[] = [event1, event2, event3];
await storage.storeEvents(events);Example 2: Using IndexedDB for Large Datasets (Local Only)
import { StorageManager, IndexedDBAdapter } from '@inputless/storage';
import type { BaseEvent } from '@inputless/events';
// Initialize storage manager with IndexedDB (local only - for large datasets)
const storage = new StorageManager({
adapter: new IndexedDBAdapter({
dbName: 'myapp_analytics',
storeName: 'events',
version: 1,
}),
maxSize: 50000, // Larger capacity
retentionDays: 30,
compression: true,
compressionAlgorithm: 'gzip', // 'gzip' | 'brotli'
batchSize: 100,
debug: true,
});
// IndexedDB adapter provides optimized queries
// Uses IndexedDB indexes if available
const sessionEvents = await storage.getEvents('session-123');
// Uses IndexedDB date index if available
const dateRangeEvents = await storage.getEventsByDateRange(
new Date('2024-01-01'),
new Date('2024-01-31')
);Example 3: Session Storage for Temporary Data (Local Only)
import { StorageManager, SessionStorageAdapter } from '@inputless/storage';
import type { BaseEvent } from '@inputless/events';
// Initialize storage manager with SessionStorage (local only - session-scoped)
const storage = new StorageManager({
adapter: new SessionStorageAdapter(),
maxSize: 1000,
retentionDays: 0, // No retention (session-scoped)
debug: true,
});
// Store events (cleared when tab closes)
const event: BaseEvent = {
id: 'event-1',
type: 'ui.click',
timestamp: Date.now(),
sessionId: 'session-123',
};
await storage.storeEvent(event);
// Events are automatically cleared when browser tab closesExample 4: Storing Events (Local Only)
import { StorageManager, LocalStorageAdapter } from '@inputless/storage';
import type { BaseEvent } from '@inputless/events';
import { generateEventId } from '@inputless/events';
const storage = new StorageManager({
adapter: new LocalStorageAdapter(),
maxSize: 10000,
retentionDays: 30,
});
// Store single event
const event: BaseEvent = {
id: generateEventId(),
type: 'ui.click',
timestamp: Date.now(),
sessionId: 'session-123',
// ... other event properties
};
await storage.storeEvent(event);
// Store multiple events (batch)
// Events are processed in batches (default batchSize: 100)
const events: BaseEvent[] = [
{ id: generateEventId(), type: 'ui.click', timestamp: Date.now(), sessionId: 'session-123' },
{ id: generateEventId(), type: 'ui.scroll', timestamp: Date.now(), sessionId: 'session-123' },
{ id: generateEventId(), type: 'form.submit', timestamp: Date.now(), sessionId: 'session-123' },
];
await storage.storeEvents(events);
// Batch operations handle compression and quota management automaticallyExample 5: Retrieving Events (Local Only)
import { StorageManager, IndexedDBAdapter } from '@inputless/storage';
import type { BaseEvent } from '@inputless/events';
import type { EventType } from '@inputless/events';
const storage = new StorageManager({
adapter: new IndexedDBAdapter({
dbName: 'analytics_db',
storeName: 'events',
}),
maxSize: 50000,
retentionDays: 30,
});
// Get events by session ID
// Uses IndexedDB sessionId index if available for optimized query
const sessionEvents: BaseEvent[] = await storage.getEvents('session-id-123');
console.log(`Found ${sessionEvents.length} events for session`);
// Get events by date range
// Uses IndexedDB timestamp index if available for optimized query
const startDate = new Date('2024-01-01');
const endDate = new Date('2024-01-31');
const dateRangeEvents: BaseEvent[] = await storage.getEventsByDateRange(startDate, endDate);
console.log(`Found ${dateRangeEvents.length} events in date range`);
// Get events by type
// Uses IndexedDB eventType index if available for optimized query
const eventType: EventType = 'ui.click';
const clickEvents: BaseEvent[] = await storage.getEventsByType(eventType);
console.log(`Found ${clickEvents.length} click events`);
// All retrieved events are sorted by timestamp (ascending)Example 6: Storage Statistics (Local Only)
import { StorageManager, LocalStorageAdapter } from '@inputless/storage';
import type { StorageStatistics } from '@inputless/storage';
const storage = new StorageManager({
adapter: new LocalStorageAdapter(),
maxSize: 10000,
retentionDays: 30,
});
// Get storage statistics
const stats: StorageStatistics = await storage.getStatistics();
console.log(`Total events: ${stats.totalEvents}`);
console.log(`Total size: ${stats.totalSize} bytes`);
console.log(`Events by session:`, stats.eventsBySession); // Map<string, number>
console.log(`Events by type:`, stats.eventsByType); // Map<EventType, number>
console.log(`Oldest event: ${stats.oldestEventTimestamp ? new Date(stats.oldestEventTimestamp) : 'N/A'}`);
console.log(`Newest event: ${stats.newestEventTimestamp ? new Date(stats.newestEventTimestamp) : 'N/A'}`);
console.log(`Expired events: ${stats.expiredEvents}`);
// Iterate over events by session
for (const [sessionId, count] of stats.eventsBySession) {
console.log(`Session ${sessionId}: ${count} events`);
}
// Iterate over events by type
for (const [eventType, count] of stats.eventsByType) {
console.log(`Type ${eventType}: ${count} events`);
}Example 7: Quota Management (Local Only)
import { StorageManager, LocalStorageAdapter } from '@inputless/storage';
import { QuotaExceededError } from '@inputless/storage';
const storage = new StorageManager({
adapter: new LocalStorageAdapter(),
maxSize: 10000,
retentionDays: 30,
quotaExceededFallback: true,
quotaFreePercentage: 0.2, // Free 20% when quota exceeded
});
// Check storage availability
const isAvailable: boolean = await storage.isAvailable();
console.log(`Storage available: ${isAvailable}`);
// Get storage usage
const usage: number | null = await storage.getStorageUsage(); // bytes
console.log(`Storage usage: ${usage ? `${(usage / 1024).toFixed(2)} KB` : 'Unknown'}`);
// Get remaining capacity
const remaining: number | null = await storage.getRemainingCapacity(); // bytes
console.log(`Remaining capacity: ${remaining ? `${(remaining / 1024).toFixed(2)} KB` : 'Unknown'}`);
// Store events with quota handling
try {
await storage.storeEvents(events);
} catch (error) {
if (error instanceof QuotaExceededError) {
// QuotaManager automatically attempts to free space if quotaExceededFallback is true
console.error('Quota exceeded, attempting to free space...');
// Retry after automatic cleanup
}
}Example 8: Manual Cleanup (Local Only)
import { StorageManager, LocalStorageAdapter } from '@inputless/storage';
const storage = new StorageManager({
adapter: new LocalStorageAdapter(),
maxSize: 10000,
retentionDays: 30,
cleanupInterval: 3600000, // 1 hour
});
// Delete expired events manually
// Returns number of events deleted
const deletedCount: number = await storage.deleteExpiredEvents();
console.log(`Deleted ${deletedCount} expired events`);
// Delete events by session
await storage.deleteEvents('session-id-123');
console.log('Deleted all events for session');
// Delete events by date range
const startDate = new Date('2024-01-01');
const endDate = new Date('2024-01-31');
await storage.deleteEventsByDateRange(startDate, endDate);
console.log('Deleted events in date range');
// Clear all events
await storage.clearAll();
console.log('Cleared all events from storage');
// Automatic cleanup runs every cleanupInterval milliseconds if retentionDays > 0Configuration
Example 10: Complete Configuration (Local Only)
import { StorageManager, IndexedDBAdapter } from '@inputless/storage';
import type { StorageConfig } from '@inputless/storage';
// Complete storage configuration
const config: StorageConfig = {
adapter: new IndexedDBAdapter({ // required
dbName: 'myapp_analytics',
storeName: 'events',
version: 1,
}),
maxSize: 50000, // default: 10000
retentionDays: 30, // default: 30, 0 = disabled
compression: true, // default: false
compressionAlgorithm: 'gzip', // 'gzip' | 'brotli', default: 'gzip'
batchSize: 100, // default: 100
keyPrefix: 'myapp_', // default: 'inputless_'
quotaExceededFallback: true, // default: true
quotaFreePercentage: 0.2, // default: 0.2, 0-1
cleanupInterval: 3600000, // default: 3600000 = 1 hour
debug: true, // default: false
};
const storage = new StorageManager(config);Storage Adapters
LocalStorageAdapter
Fast, synchronous storage for small datasets (<5MB).
Features:
- Synchronous operations
- Fast access (<10ms)
- Limited storage (~5-10MB)
- No indexing support
Use Case: Small datasets, fast reads, simple queries
IndexedDBAdapter
Async storage for larger datasets with indexing support.
Features:
- Asynchronous operations
- Large storage capacity (~50MB+)
- Indexing support for efficient queries
- Transaction support
- Optimized queries by session, date range, event type
Use Case: Large datasets, complex queries, offline support
SessionStorageAdapter
Session-scoped storage that clears on tab close.
Features:
- Synchronous operations
- Fast access
- Session-only persistence
- Limited storage (~5-10MB)
Use Case: Temporary data, session-scoped analytics
MemoryAdapter
In-memory storage for testing and development.
Features:
- No persistence (data lost when instance destroyed)
- Fast access
- Useful for testing
Use Case: Unit tests, development, mocking
Example:
import { StorageManager, MemoryAdapter } from '@inputless/storage';
const storage = new StorageManager({
adapter: new MemoryAdapter({ prefix: 'test_' }),
maxSize: 1000,
retentionDays: 0,
});API Reference
API from src/index.ts and src/StorageManager.ts:
StorageManager
Constructor
new StorageManager(config: StorageConfig)config:StorageConfig
Methods
storeEvent(event: BaseEvent): Promise<void>- Store single eventstoreEvents(events: BaseEvent[]): Promise<void>- Store multiple eventsgetEvents(sessionId: string): Promise<BaseEvent[]>- Get events by sessiongetEventsByDateRange(startDate: Date, endDate: Date): Promise<BaseEvent[]>- Get events by date rangegetEventsByType(eventType: EventType): Promise<BaseEvent[]>- Get events by typedeleteEvents(sessionId: string): Promise<void>- Delete events by sessiondeleteEventsByDateRange(startDate: Date, endDate: Date): Promise<void>- Delete events by date rangedeleteExpiredEvents(): Promise<number>- Delete expired events, returns countgetStatistics(): Promise<StorageStatistics>- Get storage statisticsclearAll(): Promise<void>- Clear all eventsgetStorageUsage(): Promise<number | null>- Get storage usage in bytesisAvailable(): Promise<boolean>- Check if storage is availablegetRemainingCapacity(): Promise<number | null>- Get remaining capacity in bytes
Error Handling
The storage module throws typed errors:
import {
StorageError,
QuotaExceededError,
StorageUnavailableError,
} from '@inputless/storage';
try {
await storage.storeEvents(events);
} catch (error) {
if (error instanceof QuotaExceededError) {
// Handle quota exceeded (fallback will attempt to free space)
console.error('Storage quota exceeded:', error.message);
} else if (error instanceof StorageUnavailableError) {
// Handle storage unavailable
console.error('Storage unavailable:', error.message);
} else if (error instanceof StorageError) {
// Handle general storage error
console.error('Storage error:', error.message);
}
}Example 11: Complete Local Integration - All Features Working Together
import { StorageManager, IndexedDBAdapter } from '@inputless/storage';
import { InputlessTracker } from '@inputless/tracker';
import type { BaseEvent } from '@inputless/events';
import type { StorageStatistics } from '@inputless/storage';
import { QuotaExceededError, StorageUnavailableError } from '@inputless/storage';
// Initialize storage manager with all features (local only)
const storage = new StorageManager({
adapter: new IndexedDBAdapter({
dbName: 'analytics_db',
storeName: 'events',
version: 1,
}),
maxSize: 10000,
retentionDays: 30,
compression: true,
compressionAlgorithm: 'gzip',
batchSize: 100,
quotaExceededFallback: true,
quotaFreePercentage: 0.2,
cleanupInterval: 3600000,
debug: true,
});
// Initialize tracker (local only)
const tracker = new InputlessTracker({
enabledCategories: { ui: true, form: true, performance: true },
debug: true,
});
// Store events from tracker
tracker.onEvent(async (event: BaseEvent) => {
try {
await storage.storeEvent(event);
} catch (error) {
if (error instanceof QuotaExceededError) {
console.warn('Quota exceeded, automatic cleanup triggered');
} else if (error instanceof StorageUnavailableError) {
console.error('Storage unavailable');
}
}
});
tracker.start();
// Periodically check storage statistics
setInterval(async () => {
const stats: StorageStatistics = await storage.getStatistics();
console.log('Storage Stats:', {
totalEvents: stats.totalEvents,
totalSize: `${(stats.totalSize / 1024).toFixed(2)} KB`,
expiredEvents: stats.expiredEvents,
});
// Check quota
const usage = await storage.getStorageUsage();
const remaining = await storage.getRemainingCapacity();
console.log('Quota:', {
usage: usage ? `${(usage! / 1024).toFixed(2)} KB` : 'Unknown',
remaining: remaining ? `${(remaining! / 1024).toFixed(2)} KB` : 'Unknown',
});
}, 60000); // Every minute
// Retrieve and process events
async function processStoredEvents() {
// Get events by session
const sessionEvents = await storage.getEvents('session-123');
console.log(`Processing ${sessionEvents.length} events from session`);
// Get events by date range
const today = new Date();
const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000);
const recentEvents = await storage.getEventsByDateRange(yesterday, today);
console.log(`Found ${recentEvents.length} events in last 24 hours`);
// Get events by type
const clickEvents = await storage.getEventsByType('ui.click');
console.log(`Found ${clickEvents.length} click events`);
// Process events locally
recentEvents.forEach(event => {
console.log(`Event: ${event.type} at ${new Date(event.timestamp)}`);
});
}
// Manual cleanup
async function cleanupOldEvents() {
const deletedCount = await storage.deleteExpiredEvents();
console.log(`Cleaned up ${deletedCount} expired events`);
}
// Run cleanup on demand
cleanupOldEvents();Example 9: Integration with Tracker (Local Only)
import { InputlessTracker } from '@inputless/tracker';
import { StorageManager, IndexedDBAdapter } from '@inputless/storage';
import type { BaseEvent } from '@inputless/events';
// Initialize tracker (local only)
const tracker = new InputlessTracker({
enabledCategories: { ui: true, form: true, performance: true },
debug: true,
});
// Initialize storage manager (local only)
const storage = new StorageManager({
adapter: new IndexedDBAdapter({
dbName: 'analytics_db',
storeName: 'events',
version: 1,
}),
maxSize: 10000,
retentionDays: 30,
compression: true,
compressionAlgorithm: 'gzip',
debug: true,
});
// Store events from tracker
tracker.onEvent(async (event: BaseEvent) => {
await storage.storeEvent(event);
});
tracker.start();
// Retrieve events for offline processing
const sessionId = 'session-123';
const events: BaseEvent[] = await storage.getEvents(sessionId);
console.log(`Retrieved ${events.length} events for processing`);
// Process events locally (no backend sync)
events.forEach(event => {
console.log(`Event: ${event.type} at ${new Date(event.timestamp)}`);
});Example 12: Integration with Tracker and Cognitive SDK (Local Only)
import { InputlessTracker } from '@inputless/tracker';
import { CognitiveSDK } from '@inputless/sdk-cognitive';
import { StorageManager, IndexedDBAdapter } from '@inputless/storage';
import type { BaseEvent } from '@inputless/events';
// Initialize cognitive SDK (local only)
const cognitiveSDK = new CognitiveSDK({
debug: true,
});
await cognitiveSDK.start();
// Initialize tracker (local only)
const tracker = new InputlessTracker({
enabledCategories: { ui: true, form: true, performance: true },
debug: true,
});
// Initialize storage manager (local only)
const storage = new StorageManager({
adapter: new IndexedDBAdapter({
dbName: 'analytics_db',
storeName: 'events',
}),
maxSize: 10000,
retentionDays: 30,
compression: true,
debug: true,
});
// Process events: Tracker → Cognitive SDK → Storage
tracker.onEvent(async (event: BaseEvent) => {
// Process through cognitive SDK
const enriched = await cognitiveSDK.perceive(event);
if (enriched) {
// Store enriched event
await storage.storeEvent(enriched);
// Access cognitive insights
if (enriched.cog) {
console.log('Stored event with intent:', enriched.cog.intent_state);
}
}
});
tracker.start();
// Later: Retrieve and analyze stored events
async function analyzeStoredEvents() {
// Get events by session
const sessionEvents = await storage.getEvents('session-123');
// Get events by type
const clickEvents = await storage.getEventsByType('ui.click');
// Get events by date range
const today = new Date();
const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000);
const recentEvents = await storage.getEventsByDateRange(yesterday, today);
console.log(`Session events: ${sessionEvents.length}`);
console.log(`Click events: ${clickEvents.length}`);
console.log(`Recent events: ${recentEvents.length}`);
// Process events locally
recentEvents.forEach(event => {
if (event.cog) {
console.log(`Event ${event.type}: intent=${event.cog.intent_state}, confidence=${event.cog.confidence}`);
}
});
}
analyzeStoredEvents();Example 13: Batch Event Processing with Storage (Local Only)
import { StorageManager, IndexedDBAdapter } from '@inputless/storage';
import type { BaseEvent } from '@inputless/events';
import { generateEventId } from '@inputless/events';
// Initialize storage manager (local only)
const storage = new StorageManager({
adapter: new IndexedDBAdapter({
dbName: 'analytics_db',
storeName: 'events',
}),
maxSize: 50000,
retentionDays: 30,
batchSize: 100, // process 100 events per batch
compression: true,
compressionAlgorithm: 'gzip',
debug: true,
});
// Generate a large batch of events
const sessionId = 'session-123';
const events: BaseEvent[] = [];
for (let i = 0; i < 500; i++) {
events.push({
id: generateEventId(),
type: i % 2 === 0 ? 'ui.click' : 'ui.scroll',
timestamp: Date.now() - (500 - i) * 1000, // Spread over time
sessionId,
});
}
// Store events in batches
// Events are automatically processed in batches of 100
await storage.storeEvents(events);
console.log(`Stored ${events.length} events in batches`);
// Retrieve all events for the session
const storedEvents = await storage.getEvents(sessionId);
console.log(`Retrieved ${storedEvents.length} events from storage`);
// Get statistics
const stats = await storage.getStatistics();
console.log('Storage Statistics:', {
totalEvents: stats.totalEvents,
totalSize: `${(stats.totalSize / 1024).toFixed(2)} KB`,
eventsBySession: Object.fromEntries(stats.eventsBySession),
eventsByType: Object.fromEntries(stats.eventsByType),
});Example 14: Event Querying and Filtering (Local Only)
import { StorageManager, IndexedDBAdapter } from '@inputless/storage';
import type { BaseEvent, EventType } from '@inputless/events';
import { generateEventId } from '@inputless/events';
// Initialize storage manager (local only)
const storage = new StorageManager({
adapter: new IndexedDBAdapter({
dbName: 'analytics_db',
storeName: 'events',
}),
maxSize: 10000,
retentionDays: 30,
debug: true,
});
// Store sample events
const session1 = 'session-1';
const session2 = 'session-2';
const events: BaseEvent[] = [
{ id: generateEventId(), type: 'ui.click', timestamp: Date.now() - 3600000, sessionId: session1 },
{ id: generateEventId(), type: 'ui.scroll', timestamp: Date.now() - 1800000, sessionId: session1 },
{ id: generateEventId(), type: 'form.submit', timestamp: Date.now() - 600000, sessionId: session2 },
{ id: generateEventId(), type: 'ui.click', timestamp: Date.now() - 300000, sessionId: session2 },
{ id: generateEventId(), type: 'ui.click', timestamp: Date.now(), sessionId: session1 },
];
await storage.storeEvents(events);
// Query 1: Get events by session
const session1Events: BaseEvent[] = await storage.getEvents(session1);
console.log(`Session 1 events: ${session1Events.length}`);
// Query 2: Get events by type
const clickEvents: BaseEvent[] = await storage.getEventsByType('ui.click' as EventType);
console.log(`Click events: ${clickEvents.length}`);
// Query 3: Get events by date range
const oneHourAgo = new Date(Date.now() - 3600000);
const now = new Date();
const recentEvents: BaseEvent[] = await storage.getEventsByDateRange(oneHourAgo, now);
console.log(`Events in last hour: ${recentEvents.length}`);
// Query 4: Combine queries manually
const allEvents = await storage.getEvents(session1);
const filteredEvents = allEvents.filter(event => event.type === 'ui.click');
console.log(`Filtered click events from session 1: ${filteredEvents.length}`);
// All retrieved events are sorted by timestamp (ascending)
console.log('Events sorted by timestamp:', recentEvents.map(e => ({
type: e.type,
timestamp: new Date(e.timestamp).toISOString(),
})));Example 15: Storage Cleanup and Maintenance (Local Only)
import { StorageManager, IndexedDBAdapter } from '@inputless/storage';
import type { BaseEvent } from '@inputless/events';
import { generateEventId } from '@inputless/events';
// Initialize storage manager with retention (local only)
const storage = new StorageManager({
adapter: new IndexedDBAdapter({
dbName: 'analytics_db',
storeName: 'events',
}),
maxSize: 10000,
retentionDays: 7, // events expire after 7 days
cleanupInterval: 3600000, // cleanup every hour
debug: true,
});
// Store events with different timestamps
const now = Date.now();
const events: BaseEvent[] = [
// Recent events (not expired)
{ id: generateEventId(), type: 'ui.click', timestamp: now - 86400000, sessionId: 'session-1' }, // 1 day ago
{ id: generateEventId(), type: 'ui.click', timestamp: now - 3600000, sessionId: 'session-1' }, // 1 hour ago
{ id: generateEventId(), type: 'ui.click', timestamp: now, sessionId: 'session-1' }, // now
// Old events (will expire)
{ id: generateEventId(), type: 'ui.click', timestamp: now - 8 * 86400000, sessionId: 'session-2' }, // 8 days ago
{ id: generateEventId(), type: 'ui.click', timestamp: now - 10 * 86400000, sessionId: 'session-2' }, // 10 days ago
];
await storage.storeEvents(events);
// Get statistics before cleanup
let stats = await storage.getStatistics();
console.log('Before cleanup:', {
totalEvents: stats.totalEvents,
expiredEvents: stats.expiredEvents,
});
// Manual cleanup of expired events
// Returns number of events deleted
const deletedCount: number = await storage.deleteExpiredEvents();
console.log(`Deleted ${deletedCount} expired events`);
// Get statistics after cleanup
stats = await storage.getStatistics();
console.log('After cleanup:', {
totalEvents: stats.totalEvents,
expiredEvents: stats.expiredEvents,
});
// Delete events by session
await storage.deleteEvents('session-2');
console.log('Deleted all events for session-2');
// Delete events by date range
const yesterday = new Date(now - 86400000);
const today = new Date(now);
await storage.deleteEventsByDateRange(yesterday, today);
console.log('Deleted events from yesterday to today');
// Clear all events
await storage.clearAll();
console.log('Cleared all events from storage');Example 16: Storage Quota Monitoring (Local Only)
import { StorageManager, LocalStorageAdapter } from '@inputless/storage';
import type { BaseEvent } from '@inputless/events';
import { QuotaExceededError, StorageUnavailableError } from '@inputless/storage';
import { generateEventId } from '@inputless/events';
// Initialize storage manager with quota management (local only)
const storage = new StorageManager({
adapter: new LocalStorageAdapter(),
maxSize: 5000,
retentionDays: 30,
quotaExceededFallback: true, // auto-free space on quota exceeded
quotaFreePercentage: 0.2, // free 20% when quota exceeded
debug: true,
});
// Check storage availability
const isAvailable: boolean = await storage.isAvailable();
console.log(`Storage available: ${isAvailable}`);
// Get storage usage
const usage: number | null = await storage.getStorageUsage(); // bytes
console.log(`Storage usage: ${usage ? `${(usage! / 1024).toFixed(2)} KB` : 'Unknown'}`);
// Get remaining capacity
const remaining: number | null = await storage.getRemainingCapacity(); // bytes
console.log(`Remaining capacity: ${remaining ? `${(remaining! / 1024).toFixed(2)} KB` : 'Unknown'}`);
// Monitor quota periodically
setInterval(async () => {
const usage = await storage.getStorageUsage();
const remaining = await storage.getRemainingCapacity();
const stats = await storage.getStatistics();
console.log('Quota Status:', {
usage: usage ? `${(usage! / 1024).toFixed(2)} KB` : 'Unknown',
remaining: remaining ? `${(remaining! / 1024).toFixed(2)} KB` : 'Unknown',
totalEvents: stats.totalEvents,
expiredEvents: stats.expiredEvents,
});
// Warn if storage is getting full
if (usage && remaining && usage > remaining * 0.8) {
console.warn('Storage is getting full! Consider cleaning up old events.');
}
}, 60000); // Every minute
// Store events with quota handling
const events: BaseEvent[] = Array.from({ length: 100 }, (_, i) => ({
id: generateEventId(),
type: 'ui.click',
timestamp: Date.now() - i * 1000,
sessionId: 'session-123',
}));
try {
await storage.storeEvents(events);
console.log('Events stored successfully');
} catch (error) {
if (error instanceof QuotaExceededError) {
// QuotaManager automatically attempts to free space if quotaExceededFallback is true
console.error('Quota exceeded, automatic cleanup triggered');
// Retry after automatic cleanup
try {
await storage.storeEvents(events);
console.log('Events stored after cleanup');
} catch (retryError) {
console.error('Failed to store events after cleanup:', retryError);
}
} else if (error instanceof StorageUnavailableError) {
console.error('Storage unavailable:', error.message);
} else {
console.error('Storage error:', error);
}
}Performance Considerations
Targets
- localStorage operations: <10ms per operation
- IndexedDB operations: <50ms per operation
- Batch operations: <100ms per 100 events
- Compression: <20ms per event (if enabled)
Optimization Tips
- Use IndexedDB for large datasets (>1000 events)
- Enable compression for space efficiency (if supported)
- Use batch operations for multiple events
- Set appropriate retention to avoid storage bloat
- Monitor quota and adjust
maxSizeaccordingly
Module Structure
packages/typescript-core/storage/
├── src/
│ ├── StorageManager.ts # Main storage manager
│ ├── StorageConfig.ts # Configuration types
│ ├── StorageAdapter.ts # Adapter interface
│ ├── adapters/
│ │ ├── LocalStorageAdapter.ts # localStorage implementation
│ │ ├── IndexedDBAdapter.ts # IndexedDB implementation
│ │ ├── SessionStorageAdapter.ts # SessionStorage implementation
│ │ └── MemoryAdapter.ts # In-memory adapter (testing)
│ ├── utils/
│ │ ├── CompressionUtils.ts # Compression utilities
│ │ ├── SerializationUtils.ts # Serialization helpers
│ │ ├── QuotaUtils.ts # Quota management utilities
│ │ └── RetentionUtils.ts # Retention policy utilities
│ ├── types/
│ │ ├── StorageTypes.ts # Storage type definitions
│ │ └── StorageErrors.ts # Error types
│ └── index.ts # Public API exportsExports
Exports from src/index.ts:
Main Classes
StorageManager- Main storage manager classLocalStorageAdapter- localStorage adapterIndexedDBAdapter- IndexedDB adapterSessionStorageAdapter- SessionStorage adapterMemoryAdapter- In-memory adapter (testing)
Types
StorageAdapter- Adapter interfaceStorageConfig- Configuration interfaceStoredEvent- Stored event formatStorageStatistics- Statistics interfaceStorageKeyType- Storage key type ('events' | 'sessions' | 'metadata')
Error Classes
StorageError- Base error classQuotaExceededError- Quota exceeded errorStorageUnavailableError- Storage unavailable error
Utilities
CompressionUtils- Compression utilitiesRetentionCleaner- Retention policy managerQuotaManager- Quota management utilitiesserialize,deserialize,isSerializable- Serialization helpers
Distribution
npm package: @inputless/storage
Version: 1.0.0+
Registry: npm
Related Documentation
doc/STORAGE_COMPONENT_IMPLEMENTATION.md- Complete implementation specificationdoc/CODE_STYLE_GUIDE.md- Code style guidelines@inputless/events- Event type definitions
Testing
The storage module includes comprehensive unit tests:
- 117 unit tests covering all adapters, utilities, and StorageManager
- Integration tests for full storage workflows
- Coverage: >50% global coverage, >70% for adapters and utilities
- Test Suites: 10 test suites, all passing
Run tests:
npm testLast Updated: January 2024
Status: ✅ Implemented, Tested, and Ready for Use
