Now.js Framework Documentation
StorageManager
StorageManager
Overview
StorageManager is the browser data storage system in Now.js Framework. It supports IndexedDB, LocalStorage, SessionStorage, and Memory Storage with advanced features.
When to use:
- Need to store data on the client side (offline storage)
- Need CRUD operations with IndexedDB
- Need data caching
- Need querying and indexing data
Why use it:
- ✅ Supports IndexedDB with object stores and indexes
- ✅ CRUD operations (add, update, getById, getAll, delete)
- ✅ Query with filtering, sorting, pagination
- ✅ Automatic caching and cache invalidation
- ✅ Retry mechanism for failed operations
- ✅ Statistics tracking
- ✅ Multiple database and store support
Basic Usage
Initialization
// Initialize with desired stores
await StorageManager.init({
defaultDB: 'my_app',
defaultVersion: 1,
stores: {
users: {
keyPath: 'id',
autoIncrement: true,
indexes: [
{ name: 'email', keyPath: 'email', options: { unique: true } },
{ name: 'name', keyPath: 'name' }
]
},
products: {
keyPath: 'id',
autoIncrement: true,
indexes: [
{ name: 'category', keyPath: 'category' },
{ name: 'price', keyPath: 'price' }
]
}
}
});Adding Data
// Add single item
const userId = await StorageManager.add('users', {
name: 'John Doe',
email: 'john@example.com',
role: 'admin'
});
console.log('User ID:', userId); // 1
// Add multiple items
const productIds = await StorageManager.add('products', [
{ name: 'iPhone', price: 30000, category: 'electronics' },
{ name: 'MacBook', price: 50000, category: 'electronics' },
{ name: 'Shirt', price: 500, category: 'clothing' }
]);
console.log('Product IDs:', productIds); // [1, 2, 3]Reading Data
// Read by ID
const user = await StorageManager.getById('users', 1);
console.log(user); // { id: 1, name: 'John Doe', ... }
// Read all
const allUsers = await StorageManager.getAll('users');
console.log(allUsers); // [{ id: 1, ... }, { id: 2, ... }]
// Read with options
const products = await StorageManager.getAll('products', {
limit: 10,
offset: 0,
direction: 'next'
});Updating Data
// Update single item (must have id)
await StorageManager.update('users', {
id: 1,
name: 'John Smith',
email: 'john.smith@example.com',
role: 'admin'
});
// Update multiple items
await StorageManager.update('products', [
{ id: 1, name: 'iPhone 15', price: 35000 },
{ id: 2, name: 'MacBook Pro', price: 55000 }
]);Deleting Data
// Delete by ID
await StorageManager.delete('users', 1);
// Clear entire store
await StorageManager.clear('users');Query and Filtering
Query with Index
// Query using index
const electronics = await StorageManager.query('products', {
index: 'category',
value: 'electronics'
});
// Query with range
const expensiveProducts = await StorageManager.query('products', {
index: 'price',
range: IDBKeyRange.lowerBound(10000)
});
// Query with options
const results = await StorageManager.query('products', {
index: 'category',
value: 'electronics'
}, {
limit: 5,
direction: 'prev' // Sort descending
});Filter Function
// Use filter function for complex conditions
const activeAdmins = await StorageManager.getAll('users', {
filter: (user) => user.role === 'admin' && user.active === true
});
// Filter with pagination
const paginatedResults = await StorageManager.getAll('products', {
filter: (product) => product.price > 1000,
limit: 10,
offset: 20
});Count
// Count all
const totalUsers = await StorageManager.count('users');
// Count with index
const adminCount = await StorageManager.count('users', {
index: 'role',
value: 'admin'
});
// Count with range
const expensiveCount = await StorageManager.count('products', {
index: 'price',
range: IDBKeyRange.lowerBound(10000)
});Database and Stores
Creating a Database
// Create new database
await StorageManager.createDatabase({
name: 'my_custom_db',
version: 1,
stores: {
logs: {
keyPath: 'id',
autoIncrement: true,
indexes: [
{ name: 'timestamp', keyPath: 'timestamp' },
{ name: 'level', keyPath: 'level' }
]
}
}
});
// Use with custom database
await StorageManager.add('logs', {
message: 'User logged in',
level: 'info',
timestamp: Date.now()
}, 'my_custom_db');Index Types
stores: {
users: {
keyPath: 'id',
autoIncrement: true,
indexes: [
// Unique index - values must be unique
{ name: 'email', keyPath: 'email', options: { unique: true } },
// Non-unique index
{ name: 'role', keyPath: 'role' },
// Multi-entry index (for arrays)
{ name: 'tags', keyPath: 'tags', options: { multiEntry: true } },
// Compound index
{ name: 'name_email', keyPath: ['name', 'email'] }
]
}
}KeyRange
Creating KeyRange
// Exactly one value
const exactly10 = IDBKeyRange.only(10);
// Greater than or equal to
const atLeast10 = IDBKeyRange.lowerBound(10);
// Greater than (exclusive)
const moreThan10 = IDBKeyRange.lowerBound(10, true);
// Less than or equal to
const atMost100 = IDBKeyRange.upperBound(100);
// Less than (exclusive)
const lessThan100 = IDBKeyRange.upperBound(100, true);
// Between (inclusive)
const between10And100 = IDBKeyRange.bound(10, 100);
// Between (exclusive)
const between10And100Exclusive = IDBKeyRange.bound(10, 100, true, true);KeyRange Examples
// Products priced $1000-$5000
const affordable = await StorageManager.query('products', {
index: 'price',
range: IDBKeyRange.bound(1000, 5000)
});
// Products starting with "A"
const aProducts = await StorageManager.query('products', {
index: 'name',
range: IDBKeyRange.bound('A', 'B', false, true)
});
// Logs from last 24 hours
const oneDayAgo = Date.now() - (24 * 60 * 60 * 1000);
const recentLogs = await StorageManager.query('logs', {
index: 'timestamp',
range: IDBKeyRange.lowerBound(oneDayAgo)
});Caching
StorageManager has built-in caching for read operations:
// Enable caching (default)
await StorageManager.init({
cacheLookups: true,
cacheMaxAge: 60 * 1000 // 1 minute
});
// getById uses cache automatically
const user1 = await StorageManager.getById('users', 1); // Reads from DB
const user2 = await StorageManager.getById('users', 1); // Reads from cache
// Cache is automatically invalidated on update/delete
await StorageManager.update('users', { id: 1, name: 'New Name' });
const user3 = await StorageManager.getById('users', 1); // Reads from DBConfiguration
await StorageManager.init({
// Database settings
defaultDB: 'now_app_storage',
defaultVersion: 1,
// Retry settings
maxRetries: 3,
retryDelay: 300, // ms
// Default store config
defaultStoreConfig: {
keyPath: 'id',
autoIncrement: true,
indexes: []
},
// Warning threshold
storeSizeWarningThreshold: 50 * 1024 * 1024, // 50MB
// Cache settings
cacheLookups: true,
cacheMaxAge: 60 * 1000, // 1 minute
// Statistics
statistics: {
enabled: true,
trackOperations: true,
trackTiming: true
},
// Store definitions
stores: {
// ... store configs
}
});API Reference
StorageManager.init(options)
Initialize StorageManager
| Parameter | Type | Description |
|---|---|---|
options |
object | Configuration options |
Returns: Promise<StorageManager>
StorageManager.add(storeName, data, dbName?)
Add data
| Parameter | Type | Description |
|---|---|---|
storeName |
string | Object store name |
data |
object/array | Data to add |
dbName |
string | Database name (optional) |
Returns: Promise<any> - ID or array of IDs
const id = await StorageManager.add('users', { name: 'John' });
const ids = await StorageManager.add('users', [{ name: 'John' }, { name: 'Jane' }]);StorageManager.update(storeName, data, dbName?)
Update data
| Parameter | Type | Description |
|---|---|---|
storeName |
string | Object store name |
data |
object/array | Data to update (must have key) |
dbName |
string | Database name (optional) |
Returns: Promise<boolean>
StorageManager.getById(storeName, id, dbName?)
Read data by ID
| Parameter | Type | Description |
|---|---|---|
storeName |
string | Object store name |
id |
any | ID to read |
dbName |
string | Database name (optional) |
Returns: Promise<any|null>
StorageManager.getAll(storeName, options?, dbName?)
Read all data
| Parameter | Type | Description |
|---|---|---|
storeName |
string | Object store name |
options.limit |
number | Maximum count |
options.offset |
number | Skip count |
options.index |
string | Use index |
options.direction |
string | 'next'/'prev' |
options.filter |
function | Filter function |
dbName |
string | Database name (optional) |
Returns: Promise<Array>
StorageManager.query(storeName, query, options?, dbName?)
Query data
| Parameter | Type | Description |
|---|---|---|
storeName |
string | Object store name |
query.index |
string | Index name |
query.value |
any | Value to match |
query.range |
IDBKeyRange | Key range |
options.limit |
number | Maximum count |
options.direction |
string | Direction |
dbName |
string | Database name (optional) |
Returns: Promise<Array>
StorageManager.count(storeName, query?, dbName?)
Count data
| Parameter | Type | Description |
|---|---|---|
storeName |
string | Object store name |
query |
object | Query conditions |
dbName |
string | Database name (optional) |
Returns: Promise<number>
StorageManager.delete(storeName, id, dbName?)
Delete data
| Parameter | Type | Description |
|---|---|---|
storeName |
string | Object store name |
id |
any | ID to delete |
dbName |
string | Database name (optional) |
Returns: Promise<boolean>
StorageManager.clear(storeName, dbName?)
Delete all data in store
| Parameter | Type | Description |
|---|---|---|
storeName |
string | Object store name |
dbName |
string | Database name (optional) |
Returns: Promise<boolean>
StorageManager.createDatabase(config)
Create database
| Parameter | Type | Description |
|---|---|---|
config.name |
string | Database name |
config.version |
number | Version |
config.stores |
object | Store definitions |
Returns: Promise<IDBDatabase>
StorageManager.isSupported()
Check IndexedDB support
Returns: boolean
Real-World Examples
Offline Todo App
// Initialize
await StorageManager.init({
stores: {
todos: {
keyPath: 'id',
autoIncrement: true,
indexes: [
{ name: 'status', keyPath: 'status' },
{ name: 'dueDate', keyPath: 'dueDate' }
]
}
}
});
// Add todo
async function addTodo(text, dueDate) {
return await StorageManager.add('todos', {
text,
status: 'pending',
dueDate,
createdAt: Date.now()
});
}
// Get pending todos
async function getPendingTodos() {
return await StorageManager.query('todos', {
index: 'status',
value: 'pending'
});
}
// Mark as complete
async function completeTodo(id) {
const todo = await StorageManager.getById('todos', id);
if (todo) {
todo.status = 'completed';
todo.completedAt = Date.now();
await StorageManager.update('todos', todo);
}
}
// Get overdue todos
async function getOverdueTodos() {
const now = Date.now();
return await StorageManager.getAll('todos', {
filter: (todo) => todo.status === 'pending' && todo.dueDate < now
});
}Data Sync
// Initialize with sync tracking
await StorageManager.init({
stores: {
data: {
keyPath: 'id',
autoIncrement: true,
indexes: [
{ name: 'syncStatus', keyPath: 'syncStatus' }
]
}
}
});
// Add with sync status
async function addOfflineData(data) {
return await StorageManager.add('data', {
...data,
syncStatus: 'pending',
createdAt: Date.now()
});
}
// Get unsynchronized data
async function getUnsyncedData() {
return await StorageManager.query('data', {
index: 'syncStatus',
value: 'pending'
});
}
// Mark as synced
async function markSynced(ids) {
const updates = ids.map(id => ({
id,
syncStatus: 'synced',
syncedAt: Date.now()
}));
for (const update of updates) {
const existing = await StorageManager.getById('data', update.id);
await StorageManager.update('data', { ...existing, ...update });
}
}Common Pitfalls
⚠️ 1. IndexedDB is Async
// ❌ Doesn't work - not waiting for await
function saveUser(user) {
StorageManager.add('users', user);
console.log('Saved!'); // May not be saved yet
}
// ✅ Use async/await
async function saveUser(user) {
await StorageManager.add('users', user);
console.log('Saved!');
}⚠️ 2. Must Have Key When Updating
// ❌ Error - no id
await StorageManager.update('users', { name: 'John' });
// ✅ Must have key (the defined keyPath)
await StorageManager.update('users', { id: 1, name: 'John' });⚠️ 3. Index Must Exist Before Querying
// ❌ Error - index doesn't exist
await StorageManager.query('users', { index: 'nonexistent', value: 'x' });
// ✅ Declare index during init
stores: {
users: {
indexes: [
{ name: 'email', keyPath: 'email' }
]
}
}⚠️ 4. Browser Storage Limits
// Check available space
try {
const estimate = await navigator.storage.estimate();
console.log(`Used: ${estimate.usage} / ${estimate.quota}`);
} catch (e) {
console.log('Storage estimate not available');
}Related Documentation
- StateManager - Global state management
- ServiceWorkerManager - Offline support
- SyncManager - Data synchronization