Now.js Framework Documentation

Now.js Framework Documentation

StorageManager

TH 15 Dec 2025 08:52

StorageManager

ภาพรวม

StorageManager คือระบบจัดการข้อมูลใน Browser ของ Now.js Framework รองรับ IndexedDB, LocalStorage, SessionStorage และ Memory Storage พร้อมฟีเจอร์ขั้นสูง

ใช้เมื่อ:

  • ต้องการเก็บข้อมูลในฝั่ง client (offline storage)
  • ต้องการ CRUD operations กับ IndexedDB
  • ต้องการ caching ข้อมูล
  • ต้องการ query และ index ข้อมูล

ทำไมต้องใช้:

  • ✅ รองรับ IndexedDB พร้อม object stores และ indexes
  • ✅ CRUD operations (add, update, getById, getAll, delete)
  • ✅ Query พร้อม filtering, sorting, pagination
  • ✅ Automatic caching และ cache invalidation
  • ✅ Retry mechanism สำหรับ failed operations
  • ✅ Statistics tracking
  • ✅ Multiple database และ store support

การใช้งานพื้นฐาน

การติดตั้ง

// เริ่มต้นด้วย 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' }
      ]
    }
  }
});

เพิ่มข้อมูล

// เพิ่มข้อมูลเดียว
const userId = await StorageManager.add('users', {
  name: 'John Doe',
  email: 'john@example.com',
  role: 'admin'
});
console.log('User ID:', userId); // 1

// เพิ่มหลายรายการ
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]

อ่านข้อมูล

// อ่านด้วย ID
const user = await StorageManager.getById('users', 1);
console.log(user); // { id: 1, name: 'John Doe', ... }

// อ่านทั้งหมด
const allUsers = await StorageManager.getAll('users');
console.log(allUsers); // [{ id: 1, ... }, { id: 2, ... }]

// อ่านด้วย options
const products = await StorageManager.getAll('products', {
  limit: 10,
  offset: 0,
  direction: 'next'
});

อัพเดทข้อมูล

// อัพเดทข้อมูลเดียว (ต้องมี id)
await StorageManager.update('users', {
  id: 1,
  name: 'John Smith',
  email: 'john.smith@example.com',
  role: 'admin'
});

// อัพเดทหลายรายการ
await StorageManager.update('products', [
  { id: 1, name: 'iPhone 15', price: 35000 },
  { id: 2, name: 'MacBook Pro', price: 55000 }
]);

ลบข้อมูล

// ลบด้วย ID
await StorageManager.delete('users', 1);

// ลบทั้ง store
await StorageManager.clear('users');

Query และ Filtering

Query ด้วย Index

// Query ด้วย index
const electronics = await StorageManager.query('products', {
  index: 'category',
  value: 'electronics'
});

// Query ด้วย range
const expensiveProducts = await StorageManager.query('products', {
  index: 'price',
  range: IDBKeyRange.lowerBound(10000)
});

// Query ด้วย options
const results = await StorageManager.query('products', {
  index: 'category',
  value: 'electronics'
}, {
  limit: 5,
  direction: 'prev' // เรียงจากมากไปน้อย
});

Filter Function

// ใช้ filter function สำหรับ complex conditions
const activeAdmins = await StorageManager.getAll('users', {
  filter: (user) => user.role === 'admin' && user.active === true
});

// Filter กับ pagination
const paginatedResults = await StorageManager.getAll('products', {
  filter: (product) => product.price > 1000,
  limit: 10,
  offset: 20
});

Count

// นับทั้งหมด
const totalUsers = await StorageManager.count('users');

// นับด้วย index
const adminCount = await StorageManager.count('users', {
  index: 'role',
  value: 'admin'
});

// นับด้วย range
const expensiveCount = await StorageManager.count('products', {
  index: 'price',
  range: IDBKeyRange.lowerBound(10000)
});

Database และ Stores

สร้าง Database

// สร้าง 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' }
      ]
    }
  }
});

// ใช้งานกับ 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 - ค่าต้องไม่ซ้ำ
      { name: 'email', keyPath: 'email', options: { unique: true } },

      // Non-unique index
      { name: 'role', keyPath: 'role' },

      // Multi-entry index (สำหรับ arrays)
      { name: 'tags', keyPath: 'tags', options: { multiEntry: true } },

      // Compound index
      { name: 'name_email', keyPath: ['name', 'email'] }
    ]
  }
}

KeyRange

การสร้าง KeyRange

// เท่ากับค่าเดียว
const exactly10 = IDBKeyRange.only(10);

// มากกว่าหรือเท่ากับ
const atLeast10 = IDBKeyRange.lowerBound(10);

// มากกว่า (ไม่รวมขอบ)
const moreThan10 = IDBKeyRange.lowerBound(10, true);

// น้อยกว่าหรือเท่ากับ
const atMost100 = IDBKeyRange.upperBound(100);

// น้อยกว่า (ไม่รวมขอบ)
const lessThan100 = IDBKeyRange.upperBound(100, true);

// ระหว่าง
const between10And100 = IDBKeyRange.bound(10, 100);

// ระหว่าง (ไม่รวมขอบ)
const between10And100Exclusive = IDBKeyRange.bound(10, 100, true, true);

ตัวอย่างการใช้ KeyRange

// สินค้าราคา 1000-5000 บาท
const affordable = await StorageManager.query('products', {
  index: 'price',
  range: IDBKeyRange.bound(1000, 5000)
});

// สินค้าที่ชื่อขึ้นต้นด้วย "A"
const aProducts = await StorageManager.query('products', {
  index: 'name',
  range: IDBKeyRange.bound('A', 'B', false, true)
});

// Logs ใน 24 ชั่วโมงล่าสุด
const oneDayAgo = Date.now() - (24 * 60 * 60 * 1000);
const recentLogs = await StorageManager.query('logs', {
  index: 'timestamp',
  range: IDBKeyRange.lowerBound(oneDayAgo)
});

Caching

StorageManager มี built-in caching สำหรับ read operations:

// เปิด caching (ค่าเริ่มต้น)
await StorageManager.init({
  cacheLookups: true,
  cacheMaxAge: 60 * 1000  // 1 นาที
});

// getById ใช้ cache อัตโนมัติ
const user1 = await StorageManager.getById('users', 1); // อ่านจาก DB
const user2 = await StorageManager.getById('users', 1); // อ่านจาก cache

// Cache ถูก invalidate อัตโนมัติเมื่อ update/delete
await StorageManager.update('users', { id: 1, name: 'New Name' });
const user3 = await StorageManager.getById('users', 1); // อ่านจาก DB

การตั้งค่า

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 อ้างอิง

StorageManager.init(options)

เริ่มต้น StorageManager

Parameter Type Description
options object Configuration options

Returns: Promise<StorageManager>

StorageManager.add(storeName, data, dbName?)

เพิ่มข้อมูล

Parameter Type Description
storeName string ชื่อ object store
data object/array ข้อมูลที่จะเพิ่ม
dbName string ชื่อ database (optional)

Returns: Promise<any> - ID หรือ 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?)

อัพเดทข้อมูล

Parameter Type Description
storeName string ชื่อ object store
data object/array ข้อมูลที่จะอัพเดท (ต้องมี key)
dbName string ชื่อ database (optional)

Returns: Promise<boolean>

StorageManager.getById(storeName, id, dbName?)

อ่านข้อมูลด้วย ID

Parameter Type Description
storeName string ชื่อ object store
id any ID ที่จะอ่าน
dbName string ชื่อ database (optional)

Returns: Promise<any|null>

StorageManager.getAll(storeName, options?, dbName?)

อ่านข้อมูลทั้งหมด

Parameter Type Description
storeName string ชื่อ object store
options.limit number จำนวนสูงสุด
options.offset number ข้ามกี่รายการ
options.index string ใช้ index
options.direction string 'next'/'prev'
options.filter function Filter function
dbName string ชื่อ database (optional)

Returns: Promise<Array>

StorageManager.query(storeName, query, options?, dbName?)

Query ข้อมูล

Parameter Type Description
storeName string ชื่อ object store
query.index string ชื่อ index
query.value any ค่าที่ต้องการ match
query.range IDBKeyRange Key range
options.limit number จำนวนสูงสุด
options.direction string ทิศทาง
dbName string ชื่อ database (optional)

Returns: Promise<Array>

StorageManager.count(storeName, query?, dbName?)

นับจำนวนข้อมูล

Parameter Type Description
storeName string ชื่อ object store
query object Query conditions
dbName string ชื่อ database (optional)

Returns: Promise<number>

StorageManager.delete(storeName, id, dbName?)

ลบข้อมูล

Parameter Type Description
storeName string ชื่อ object store
id any ID ที่จะลบ
dbName string ชื่อ database (optional)

Returns: Promise<boolean>

StorageManager.clear(storeName, dbName?)

ลบข้อมูลทั้งหมดใน store

Parameter Type Description
storeName string ชื่อ object store
dbName string ชื่อ database (optional)

Returns: Promise<boolean>

StorageManager.createDatabase(config)

สร้าง database

Parameter Type Description
config.name string ชื่อ database
config.version number เวอร์ชัน
config.stores object Store definitions

Returns: Promise<IDBDatabase>

StorageManager.isSupported()

ตรวจสอบ IndexedDB support

Returns: boolean

ตัวอย่างการใช้งานจริง

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

ข้อควรระวัง

⚠️ 1. IndexedDB เป็น Async

// ❌ ไม่ทำงาน - ไม่รอ await
function saveUser(user) {
  StorageManager.add('users', user);
  console.log('Saved!'); // อาจยังไม่ save จริง
}

// ✅ ใช้ async/await
async function saveUser(user) {
  await StorageManager.add('users', user);
  console.log('Saved!');
}

⚠️ 2. ต้องมี Key เมื่อ Update

// ❌ Error - ไม่มี id
await StorageManager.update('users', { name: 'John' });

// ✅ ต้องมี key (keyPath ที่กำหนดไว้)
await StorageManager.update('users', { id: 1, name: 'John' });

⚠️ 3. Index ต้องมีอยู่ก่อน Query

// ❌ Error - index ไม่มี
await StorageManager.query('users', { index: 'nonexistent', value: 'x' });

// ✅ ประกาศ index ตอน init
stores: {
  users: {
    indexes: [
      { name: 'email', keyPath: 'email' }
    ]
  }
}

⚠️ 4. Browser Storage Limits

// ตรวจสอบพื้นที่
try {
  const estimate = await navigator.storage.estimate();
  console.log(`Used: ${estimate.usage} / ${estimate.quota}`);
} catch (e) {
  console.log('Storage estimate not available');
}

เอกสารที่เกี่ยวข้อง