Now.js Framework Documentation

Now.js Framework Documentation

StorageManager

EN 15 Dec 2025 01:22

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 DB

Configuration

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');
}