Type-safe browser storage wrapper for localStorage and sessionStorage
NodeJS package | GitHub Repository
A powerful, type-safe wrapper for browser storage (localStorage and sessionStorage) with built-in logging, validation, and error handling. Built with TypeScript for maximum type safety and developer experience.
Version 3.0.0-alpha.1 - Complete TypeScript rewrite with architectural refactoring (December 2025)
- β¨ Full TypeScript support with generic types:
DataContext<T> - π Type-safe operations - catch errors at compile time
- ποΈ Modern architecture - modular design with service orchestration
- οΏ½ Three storage options - localStorage, sessionStorage, and IndexedDB (~50MB+)
- β±οΈ Automatic timestamp tracking - transparent metadata with createdAt, updatedAt, version
- π Time-based patterns - sync-since-timestamp, cache freshness, conflict resolution- π Cross-tab synchronization (v3.2.0+) - automatic data sync between browser tabs- πΎ Storage quota checking - monitor capacity and usage across storage types
- π¦ Simple API - config object pattern or legacy constructor
- π― Zero code duplication - 300+ lines eliminated through refactoring
- β
Comprehensive validation - centralized with
ValidationUtils - π¨ 6 custom error classes - specific, actionable error handling
- πͺ΅ Built-in logging via i45-jslogger
- π§ͺ Well tested - 272 tests with excellent coverage
- π― Zero dependencies (except i45-jslogger and i45-sample-data)
- π Sample data included via i45-sample-data
- π³ Tree-shakeable ESM build
- π Comprehensive type definitions (.d.ts)
π Documentation: Migration Guide | API Reference | TypeScript Guide | Examples | Offline Sync Guide | Cross-Tab Sync Guide
npm install i45import { DataContext, StorageLocations, Logger } from "i45";
// Define your data type
interface User {
id: number;
name: string;
email: string;
}
// Create a type-safe context with config object (modern approach)
const context = new DataContext<User>({
storageKey: "Users",
storageLocation: StorageLocations.LocalStorage,
trackTimestamps: true, // Automatic metadata (default: true)
loggingEnabled: true,
logger: new Logger(),
});
// Or use legacy constructor (still supported)
const legacyContext = new DataContext<User>(
"Users",
StorageLocations.LocalStorage
);
// Store data (fully typed!)
await context.store([
{ id: 1, name: "Alice", email: "alice@example.com" },
{ id: 2, name: "Bob", email: "bob@example.com" },
]);
// Retrieve data (returns User[])
const users = await context.retrieve();
console.log(users);
// Get metadata (timestamps, version, count)
const metadata = await context.getMetadata();
console.log(`Created: ${metadata.createdAt}, Version: ${metadata.version}`);π More examples: examples.md | TypeScript Guide
import { DataContext, SampleData } from "i45";
// Create an instance of the datacontext
// The default storage location is localStorage
const context = new DataContext();
// Store data using sample data
await context.store(SampleData.Lists.Astronomy);
// Retrieve data
const data = await context.retrieve();
console.log("Astronomy terms:", data);i45 v3.0.0 features a completely refactored, modular architecture (December 2025).
π See also: Migration Guide - Architecture | API Reference
/src
/core # Core application logic
DataContext.ts # Main storage context
StorageManager.ts # Service orchestration
/services
/base # Abstract base classes
IStorageService.ts # Service interface
BaseStorageService.ts # Shared service logic
LocalStorageService.ts
SessionStorageService.ts
/errors # Custom error classes
StorageKeyError.ts
StorageLocationError.ts
DataRetrievalError.ts
StorageQuotaError.ts
PersistenceServiceNotEnabled.ts
DataServiceUnavailable.ts
/models # Data models
DataContextConfig.ts
storageItem.ts
storageLocations.ts
/utils # Shared utilities
ValidationUtils.ts # Centralized validation
ErrorHandler.ts # Error management
- Single Responsibility: Each module has one clear purpose
- Zero Duplication: 300+ lines of duplicate code eliminated
- Easy Testing: Isolated modules with 92% test coverage
- Type Safe: Strong typing throughout
- Extensible: Add new storage services by implementing interface
- TypeScript Usage
- Default Storage Settings
- Custom Storage Settings
- Retrieving Data
- Retrieving Data from Custom Data Stores
- Removing Items and Clearing the Data Store
- Storage Locations
- Using Sample Data
- Logging
i45 v3.0 is built with TypeScript and provides full type safety.
π See typescript.md for comprehensive TypeScript usage guide
import { DataContext, StorageLocations, type StorageItem } from "i45";
// Generic type for your data
interface Product {
id: string;
name: string;
price: number;
}
// Type-safe context
const context = new DataContext<Product>(
"products",
StorageLocations.SessionStorage
);
// Store - TypeScript ensures correct types
await context.store([
{ id: "1", name: "Widget", price: 9.99 },
{ id: "2", name: "Gadget", price: 19.99 },
]);
// Retrieve - returns Product[]
const products = await context.retrieve();
products.forEach((p) => console.log(`${p.name}: $${p.price}`));import { DataContext, SampleData } from "i45";
// Create an instance - uses localStorage by default with key "i45"
const context = new DataContext();
// Store data
await context.store(SampleData.Lists.Astronomy);
// Retrieve data
const data = await context.retrieve();
console.log(data);import { DataContext, StorageLocations, Logger } from "i45";
// Create context with configuration object
const context = new DataContext<BookType>({
storageKey: "Books",
storageLocation: StorageLocations.SessionStorage,
loggingEnabled: true,
logger: new Logger(),
});
// Store books collection
await context.store(SampleData.JsonData.Books);
// Retrieve data
const books = await context.retrieve();
console.log(books);import { DataContext, StorageLocations, SampleData } from "i45";
// Create context with positional parameters
const context = new DataContext("Books", StorageLocations.SessionStorage);
// Store books collection
await context.store(SampleData.JsonData.Books);
// Retrieve data
const books = await context.retrieve();
console.log(books);import { DataContext, SampleData } from "i45";
// Create context
const context = new DataContext();
// Store data
await context.store(SampleData.JsonData.States);
// Retrieve and use
const states = await context.retrieve();
console.log("State data:", states);v3.0.0 provides clear, explicit methods (no confusing overloads):
import { DataContext, StorageLocations } from "i45";
const context = new DataContext<MyType>();
// Store with different scopes
await context.store(items); // Default key/location
await context.storeAs("customKey", items); // Custom key
await context.storeAt("key", StorageLocations.SessionStorage, items); // Full control
// Retrieve with different scopes
const data1 = await context.retrieve(); // Default
const data2 = await context.retrieveFrom("customKey"); // Custom key
const data3 = await context.retrieveAt("key", StorageLocations.SessionStorage); // Full control
// Remove with different scopes
await context.remove(); // Default
await context.removeFrom("customKey"); // Custom key
await context.removeAt("key", StorageLocations.SessionStorage); // Full controlimport { DataContext, StorageLocations, SampleData } from "i45";
// Create context with custom settings
const context = new DataContext("Questions", StorageLocations.SessionStorage);
// Store questions
await context.store(SampleData.JsonData.TriviaQuestions);
// Retrieve by key
const questions = await context.retrieve("Questions");
console.log(questions);
// Retrieve with specific location
const data = await context.retrieve("MyItems", StorageLocations.LocalStorage);// Delete a specific data store by key
await context.remove("Questions");
// Clear all data from current storage location
await context.clear();To clear all entries in all storage locations, call the clear() method.
Warning: Calling the clear() method will clear all entries in all storage locations.
import { DataContext } from "i45";
var dataContext = new DataContext();
// create an array of countries using sample data.
var countries = SampleData.KeyValueLists.Countries;
// save the collection
dataContext.store("Countries", countries);
// removes the item from storage.
dataContext.remove("Countries");
// removes all items from all storage locations.
// *** WARNING *** calling clear() will clears all entries.
datacontext.clear();StorageLocations is an enum of available storage options:
import { StorageLocations } from "i45";
// Available options
StorageLocations.LocalStorage; // Uses window.localStorage (default, ~5-10MB)
StorageLocations.SessionStorage; // Uses window.sessionStorage (~5-10MB)
StorageLocations.IndexedDB; // Uses IndexedDB (~50MB+, async database)import { DataContext, StorageLocations } from "i45";
// Specify storage location in constructor
const context = new DataContext("MyItems", StorageLocations.SessionStorage);
// Or use properties
context.storageLocation = StorageLocations.LocalStorage;
// Use IndexedDB for larger datasets
const largeDataContext = new DataContext({
storageKey: "LargeDataset",
storageLocation: StorageLocations.IndexedDB,
});The i45-sample-data package provides sample datasets for development and testing:
import { SampleData } from "i45";
// Access various sample datasets
const books = SampleData.JsonData.Books;
const states = SampleData.JsonData.States;
const astronomy = SampleData.Lists.Astronomy;
const countries = SampleData.KeyValueLists.Countries;
console.log(books);i45 integrates i45-jslogger for comprehensive logging support.
π See also: examples.md - Custom Logger
import { DataContext } from "i45";
const context = new DataContext();
// Enable logging
context.loggingEnabled = true;
// Operations will now be logged
await context.store([{ id: 1, name: "Test" }]);When enabled, log messages are written to the console and stored in localStorage.
Add custom logging clients to receive DataContext events:
import { DataContext, Logger } from "i45";
// Create or use your existing logger
const customLogger = new Logger({
logToConsole: true,
logToStorage: false,
});
// Add to context
const context = new DataContext();
context.addClient(customLogger);
// Multiple loggers supported
context.addClient(fileSystemLogger);
context.addClient(apiLogger);π Complete API documentation: api.md
Main class for managing browser storage operations.
class DataContext<T = any> {
// Constructor - Config object (recommended)
constructor(config?: DataContextConfig);
// Constructor - Legacy (still supported)
constructor(storageKey?: string, storageLocation?: StorageLocation);
// Properties
storageKey: string;
storageLocation: StorageLocation;
loggingEnabled: boolean;
logger: Logger | null;
// Store methods
async store(items: T[]): Promise<DataContext<T>>;
async storeAs(storageKey: string, items: T[]): Promise<DataContext<T>>;
async storeAt(
storageKey: string,
storageLocation: StorageLocation,
items: T[]
): Promise<DataContext<T>>;
// Retrieve methods
async retrieve(): Promise<T[]>;
async retrieveFrom(storageKey: string): Promise<T[]>;
async retrieveAt(
storageKey: string,
storageLocation: StorageLocation
): Promise<T[]>;
// Remove methods
async remove(): Promise<DataContext<T>>;
async removeFrom(storageKey: string): Promise<DataContext<T>>;
async removeAt(
storageKey: string,
storageLocation: StorageLocation
): Promise<DataContext<T>>;
// Other methods
async clear(): Promise<DataContext<T>>;
addClient(logger: Logger): DataContext<T>;
getCurrentSettings(): {
storageKey: string;
storageLocation: StorageLocation;
};
getData(): any[];
printLog(): any[];
}Configuration object for DataContext (v3.0.0+):
interface DataContextConfig {
storageKey?: string; // Default: "Items"
storageLocation?: StorageLocation; // Default: localStorage
logger?: Logger | null; // Optional logger instance
loggingEnabled?: boolean; // Default: false
}// Storage location type
export enum StorageLocations {
SessionStorage = "sessionStorage",
LocalStorage = "localStorage",
}
export type StorageLocation = `${StorageLocations}`;
// Storage item interface
export interface StorageItem {
name: string;
value: string;
}
// Database settings
export interface DatabaseSettings {
storageKey: string;
storageLocation: StorageLocation;
loggingEnabled: boolean;
}v3.0.0 provides 6 custom error classes for specific error handling.
π Full error documentation: api.md - Error Classes | Examples
import {
PersistenceServiceNotEnabled,
DataServiceUnavailable,
StorageKeyError,
StorageLocationError,
DataRetrievalError,
StorageQuotaError, // NEW in December 2025
} from "i45";
try {
await context.store(data);
} catch (error) {
if (error instanceof StorageKeyError) {
console.error("Invalid storage key:", error.key);
} else if (error instanceof StorageQuotaError) {
console.error("Storage full:", error.key, error.storageType);
} else if (error instanceof DataRetrievalError) {
console.error("Failed to retrieve:", error.key, "Cause:", error.cause);
} else if (error instanceof StorageLocationError) {
console.error(
"Invalid location:",
error.location,
"Valid:",
error.validLocations
);
}
}v3.0.0 includes breaking changes and major architectural improvements. See migration.md for the complete migration guide.
- New Architecture: Modular design with service orchestration (December 2025)
- Config Object Pattern: New recommended way to initialize DataContext
- TypeScript First: Full TypeScript rewrite with generic types
- Explicit Methods:
store(),storeAs(),storeAt()instead of overloaded signatures - 6 Custom Errors: Specific error classes for better error handling
- Centralized Validation:
ValidationUtilsfor consistent validation - Zero Duplication: 300+ lines of duplicate code eliminated
- Property Names:
StorageItem.Nameβname,StorageItem.Valueβvalue(camelCase) - Async Operations: All storage operations return Promises
// v2.x (Old)
const context = new DataContext();
context.setStorageKey("MyData");
context.store(data); // May not be async
// v3.x (New - Config Object)
const context = new DataContext({
storageKey: "MyData",
loggingEnabled: true,
});
await context.store(data); // Always async
// v3.x (New - Legacy Constructor)
const context = new DataContext("MyData");
await context.store(data); // Always asyncFor detailed migration steps, error handling examples, and troubleshooting, see migration.md.
- Chrome/Edge: Latest 2 versions
- Firefox: Latest 2 versions
- Safari: Latest 2 versions
- Modern browsers with ES2015+ support
- Node.js 16+ (for development)
- Modern browser with localStorage/sessionStorage support
- React: See examples.md - React Integration
- Vue: See examples.md - Vue Integration
- TypeScript: See typescript.md for type-safe integration patterns
i45 v3.0.0 includes comprehensive testing:
- 205 tests with Jest
- 91.7% statement coverage
- Unit tests for all components including IndexedDBService
- Type safety tests
- Error handling tests
- Browser storage mocking with fake-indexeddb
# Run tests
npm test
# Run tests with coverage
npm run test:coverage
# Watch mode
npm run test:watchπ Testing examples: examples.md - Testing Examples
- README.md - This file (getting started and quick reference)
- api.md - Complete API reference with all methods, properties, and error classes
- typescript.md - TypeScript usage guide with patterns and best practices
- examples.md - 20+ comprehensive examples including React/Vue integration
- offline-sync.md - Comprehensive offline sync patterns, conflict resolution, and queue management
- migration.md - Complete v2.x β v3.x migration guide
- revisions.md - Version history and changelog
- REFACTORING-SUMMARY.md - December 2025 refactoring details
MIT Β© CIS Guru
- npm package
- GitHub Repository
- Issue Tracker
- i45-jslogger - Logging support
- i45-sample-data - Sample datasets
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
See revisions.md for version history and release notes.