کار کردن با IndexedDB

این راهنما اصول اولیه IndexedDB API را پوشش می‌دهد. ما از کتابخانه IndexedDB Promised جیک آرچیبالد استفاده می‌کنیم که بسیار شبیه به IndexedDB API است، اما از وعده‌ها استفاده می‌کند که می‌توانید برای نحو مختصرتر await کنید. این کار API را ساده می‌کند و درعین‌حال ساختار آن را حفظ می‌کند.

IndexedDB چیست؟

IndexedDB یک سیستم ذخیره‌سازی NoSQL در مقیاس بزرگ است که امکان ذخیره‌سازی تقریباً هر چیزی را در مرورگر کاربر فراهم می‌کند. علاوه‌بر کنش‌های معمول جستجو، دریافت، و قرار دادن، IndexedDB از تراکنش‌ها نیز پشتیبانی می‌کند و برای ذخیره کردن حجم زیادی از داده‌های ساختاریافته بسیار مناسب است.

هر پایگاه داده IndexedDB برای یک مبدأ (معمولاً دامنه یا زیردامنه سایت) منحصربه‌فرد است، یعنی نمی‌تواند به هیچ مبدأ دیگری دسترسی داشته باشد یا توسط آن قابل‌دسترسی باشد. محدودیت‌های ذخیره‌سازی داده آن معمولاً زیاد است (اگر اصلاً وجود داشته باشد)، اما مرورگرهای مختلف محدودیت‌ها و حذف داده‌ها را به‌طور متفاوتی مدیریت می‌کنند. برای اطلاعات بیشتر، بخش مطالعه بیشتر را ببینید.

شرایط IndexedDB

پایگاه داده
بالاترین سطح IndexedDB. این پایگاه داده حاوی مخازن شیء است که به‌نوبه خود حاوی داده‌هایی است که می‌خواهید حفظ کنید. می‌توانید چندین پایگاه داده با هر نامی که انتخاب می‌کنید ایجاد کنید.
فروشگاه اشیاء
باکت تکی برای ذخیره کردن داده‌ها، مشابه جدول‌ها در پایگاه‌های داده رابطه‌ای. معمولاً برای هر نوع (نه نوع داده جاوا اسکریپت) داده‌ای که ذخیره می‌کنید، یک مخزن شیء وجود دارد. برخلاف جدول‌های پایگاه داده، انواع داده جاوا اسکریپت در فروشگاه لازم نیست سازگار باشند. برای مثال، اگر برنامه‌ای دارای مخزن شیء people حاوی اطلاعاتی درباره سه نفر باشد، ویژگی‌های سن آن افراد می‌تواند 53، 'twenty-five'، و unknown باشد.
نمایه
نوعی مخزن شیء برای سازمان‌دهی داده‌ها در مخزن شیء دیگری (به‌نام مخزن شیء مرجع) براساس ویژگی فردی داده‌ها. از نمایه برای بازیابی سوابق در مخزن شیء براساس این ویژگی استفاده می‌شود. برای مثال، اگر افراد را ذخیره می‌کنید، ممکن است بخواهید بعداً آن‌ها را براساس نام، سن، یا حیوان موردعلاقه‌شان واکشی کنید.
عملیات
تعامل با پایگاه داده.
تراکنش
پوششی برای یک عملیات یا گروهی از عملیات‌ها که یکپارچگی پایگاه داده را تضمین می‌کند. اگر یکی از کنش‌ها در تراکنش ناموفق باشد، هیچ‌کدام از آن‌ها اعمال نمی‌شود و پایگاه داده به حالتی که قبل‌از شروع تراکنش داشت برمی‌گردد. همه عملیات خواندن یا نوشتن در IndexedDB باید بخشی از یک تراکنش باشد. این کار امکان عملیات اتمی خواندن-اصلاح-نوشتن را بدون خطر تداخل با رشته‌های دیگری که هم‌زمان روی پایگاه داده عمل می‌کنند فراهم می‌کند.
مکان‌نما
سازوکاری برای تکرار روی چندین رکورد در پایگاه داده.

نحوه بررسی پشتیبانی از IndexedDB

IndexedDB تقریباً به‌طور جهانی پشتیبانی می‌شود. بااین‌حال، اگر با مرورگرهای قدیمی‌تر کار می‌کنید، ایده بدی نیست که پشتیبانی تشخیص ویژگی را محض احتیاط اضافه کنید. آسان‌ترین راه بررسی شیء window است:

function indexedDBStuff () {
  // Check for IndexedDB support:
  if (!('indexedDB' in window)) {
    // Can't use IndexedDB
    console.log("This browser doesn't support IndexedDB");
    return;
  } else {
    // Do IndexedDB stuff here:
    // ...
  }
}

// Run IndexedDB code:
indexedDBStuff();

نحوه باز کردن پایگاه داده

با IndexedDB، می‌توانید چندین پایگاه داده با هر نامی که انتخاب می‌کنید ایجاد کنید. اگر پایگاه داده‌ای هنگام تلاش برای باز کردن آن وجود نداشته باشد، به‌طور خودکار ایجاد می‌شود. برای باز کردن پایگاه داده، از روش openDB() از کتابخانه idb استفاده کنید:

import {openDB} from 'idb';

async function useDB () {
  // Returns a promise, which makes `idb` usable with async-await.
  const dbPromise = await openDB('example-database', version, events);
}

useDB();

این روش یک promise برمی‌گرداند که به یک شیء پایگاه داده تبدیل می‌شود. هنگام استفاده از روش openDB()، نام، شماره نسخه، و شیء رویدادها را برای راه‌اندازی پایگاه داده ارائه دهید.

در اینجا مثالی از روش openDB() در بافت آورده شده است:

import {openDB} from 'idb';

async function useDB () {
  // Opens the first version of the 'test-db1' database.
  // If the database does not exist, it will be created.
  const dbPromise = await openDB('test-db1', 1);
}

useDB();

بررسی پشتیبانی IndexedDB را در بالای تابع ناشناس قرار دهید. اگر مرورگر از IndexedDB پشتیبانی نکند، این از تابع خارج می‌شود. اگر تابع بتواند ادامه دهد، روش openDB() را برای باز کردن پایگاه داده‌ای به‌نام 'test-db1' فراخوانی می‌کند. در این مثال، شیء رویدادهای اختیاری برای ساده نگه داشتن امور حذف شده است، اما برای انجام هر کار معناداری با IndexedDB باید آن را مشخص کنید.

نحوه کار با مخازن شیء

پایگاه داده IndexedDB حاوی یک یا چند ذخیره‌ساز شیء است که هرکدام یک ستون برای کلید و ستون دیگری برای داده‌های مرتبط با آن کلید دارند.

ایجاد فروشگاه‌های شیء

یک پایگاه داده IndexedDB با ساختار مناسب باید برای هر نوع داده‌ای که باید حفظ شود، یک مخزن شیء داشته باشد. برای مثال، سایتی که نمایه‌های کاربر و یادداشت‌ها را حفظ می‌کند ممکن است یک مخزن شیء people حاوی اشیاء person و یک مخزن شیء notes حاوی اشیاء note داشته باشد.

برای اطمینان از یکپارچگی پایگاه داده، فقط می‌توانید مخزن‌های شیء را در شیء رویدادها در تماس openDB() ایجاد یا حذف کنید. شیء رویدادها روش upgrade() را نشان می‌دهد که به شما امکان می‌دهد فروشگاه‌های شیء ایجاد کنید. برای ایجاد فروشگاه شیء، روش createObjectStore() را در روش upgrade() فراخوانی کنید:

import {openDB} from 'idb';

async function createStoreInDB () {
  const dbPromise = await openDB('example-database', 1, {
    upgrade (db) {
      // Creates an object store:
      db.createObjectStore('storeName', options);
    }
  });
}

createStoreInDB();

این روش نام فروشگاه شیء و یک شیء پیکربندی اختیاری را می‌گیرد که به شما امکان می‌دهد ویژگی‌های مختلفی را برای فروشگاه شیء تعریف کنید.

در زیر مثالی از نحوه استفاده از createObjectStore() آورده شده است:

import {openDB} from 'idb';

async function createStoreInDB () {
  const dbPromise = await openDB('test-db1', 1, {
    upgrade (db) {
      console.log('Creating a new object store...');

      // Checks if the object store exists:
      if (!db.objectStoreNames.contains('people')) {
        // If the object store does not exist, create it:
        db.createObjectStore('people');
      }
    }
  });
}

createStoreInDB();

در این مثال، یک شیء رویدادها به روش openDB() ارسال می‌شود تا مخزن شیء ایجاد شود، و مانند قبل، کار ایجاد مخزن شیء در روش upgrade() شیء رویداد انجام می‌شود. بااین‌حال، چون اگر بخواهید مخزن شیئی را که ازقبل وجود دارد ایجاد کنید، مرورگر خطا می‌دهد، توصیه می‌کنیم روش createObjectStore() را در عبارت if قرار دهید که بررسی می‌کند آیا مخزن شیء وجود دارد یا نه. در بلوک if، createObjectStore() را فراخوانی کنید تا فروشگاه شیء با نام 'firstOS' ایجاد شود.

نحوه تعریف کردن کلیدهای اصلی

وقتی مخازن شیء را تعریف می‌کنید، می‌توانید نحوه شناسایی منحصربه‌فرد داده‌ها را در مخزن بااستفاده از کلید اصلی تعریف کنید. می‌توانید کلید اصلی را با تعریف کردن مسیر کلید یا بااستفاده از تولیدکننده کلید تعریف کنید.

مسیر کلید ویژگی‌ای است که همیشه وجود دارد و حاوی مقداری منحصربه‌فرد است. برای مثال، در مورد مخزن شیء people، ممکن است نشانی ایمیل را به‌عنوان مسیر کلید انتخاب کنید:

import {openDB} from 'idb';

async function createStoreInDB () {
  const dbPromise = await openDB('test-db2', 1, {
    upgrade (db) {
      if (!db.objectStoreNames.contains('people')) {
        db.createObjectStore('people', { keyPath: 'email' });
      }
    }
  });
}

createStoreInDB();

این مثال یک فروشگاه شیء به‌نام 'people' ایجاد می‌کند و ویژگی email را به‌عنوان کلید اصلی در گزینه keyPath اختصاص می‌دهد.

همچنین می‌توانید از یک تولیدکننده کلید مانند autoIncrement استفاده کنید. تولیدکننده کلید برای هر شیء اضافه‌شده به مخزن شیء، مقدار یکتایی ایجاد می‌کند. به‌طور پیش‌فرض، اگر کلیدی را مشخص نکنید، IndexedDB کلیدی ایجاد می‌کند و آن را جدا از داده‌ها ذخیره می‌کند.

مثال زیر یک فروشگاه شیء به نام 'notes' ایجاد می‌کند و کلید اصلی را طوری تنظیم می‌کند که به‌طور خودکار به‌عنوان یک عدد افزایشی خودکار اختصاص داده شود:

import {openDB} from 'idb';

async function createStoreInDB () {
  const dbPromise = await openDB('test-db2', 1, {
    upgrade (db) {
      if (!db.objectStoreNames.contains('notes')) {
        db.createObjectStore('notes', { autoIncrement: true });
      }
    }
  });
}

createStoreInDB();

مثال زیر مشابه مثال قبلی است، اما این بار مقدار افزایش خودکار به‌طور صریح به ویژگی‌ای به‌نام 'id' اختصاص داده شده است.

import {openDB} from 'idb';

async function createStoreInDB () {
  const dbPromise = await openDB('test-db2', 1, {
    upgrade (db) {
      if (!db.objectStoreNames.contains('logs')) {
        db.createObjectStore('logs', { keyPath: 'id', autoIncrement: true });
      }
    }
  });
}

createStoreInDB();

انتخاب روشی که برای تعریف کلید استفاده می‌شود به داده‌های شما بستگی دارد. اگر داده‌هایتان ویژگی‌ای دارد که همیشه منحصربه‌فرد است، می‌توانید آن را به keyPath تبدیل کنید تا این منحصربه‌فرد بودن را اعمال کنید. درغیراین‌صورت، از مقدار افزایشی خودکار استفاده کنید.

کد زیر سه فروشگاه شیء ایجاد می‌کند که روش‌های مختلف تعریف کلیدهای اصلی را در فروشگاه‌های شیء نشان می‌دهد:

import {openDB} from 'idb';

async function createStoresInDB () {
  const dbPromise = await openDB('test-db2', 1, {
    upgrade (db) {
      if (!db.objectStoreNames.contains('people')) {
        db.createObjectStore('people', { keyPath: 'email' });
      }

      if (!db.objectStoreNames.contains('notes')) {
        db.createObjectStore('notes', { autoIncrement: true });
      }

      if (!db.objectStoreNames.contains('logs')) {
        db.createObjectStore('logs', { keyPath: 'id', autoIncrement: true });
      }
    }
  });
}

createStoresInDB();

نحوه تعریف نمایه‌ها

نمایه‌ها نوعی ذخیره‌سازی شیء هستند که برای بازیابی داده‌ها از ذخیره‌سازی شیء مرجع توسط یک ویژگی مشخص استفاده می‌شوند. نمایه در داخل مخزن شیء مرجع قرار دارد و حاوی همان داده‌ها است، اما از ویژگی مشخص‌شده به‌عنوان مسیر کلید خود به‌جای کلید اصلی مخزن مرجع استفاده می‌کند. نمایه‌ها باید هنگام ایجاد فروشگاه‌های شیء ساخته شوند و می‌توان از آن‌ها برای تعریف محدودیت منحصربه‌فرد در داده‌هایتان استفاده کرد.

برای ایجاد نمایه، روش createIndex() را در نمونه ذخیره شیء فراخوانی کنید:

import {openDB} from 'idb';

async function createIndexInStore() {
  const dbPromise = await openDB('storeName', 1, {
    upgrade (db) {
      const objectStore = db.createObjectStore('storeName');

      objectStore.createIndex('indexName', 'property', options);
    }
  });
}

createIndexInStore();

این روش یک شیء نمایه ایجاد می‌کند و برمی‌گرداند. روش createIndex() در نمونه ذخیره شیء، نام نمایه جدید را به‌عنوان اولین آرگومان می‌گیرد و آرگومان دوم به ویژگی داده‌ای که می‌خواهید نمایه‌سازی کنید اشاره دارد. آرگومان نهایی به شما امکان می‌دهد دو گزینه را تعریف کنید که نحوه عملکرد نمایه را تعیین می‌کنند: unique و multiEntry. اگر unique روی true تنظیم شده باشد، نمایه مقادیر تکراری را برای یک کلید مجاز نمی‌داند. سپس، multiEntry تعیین می‌کند که وقتی ویژگی نمایه‌شده آرایه است، createIndex() چگونه رفتار کند. اگر روی true تنظیم شده باشد، createIndex() برای هر عنصر آرایه، ورودی‌ای به نمایه اضافه می‌کند. درغیراین‌صورت، یک ورودی تکی حاوی آرایه اضافه می‌کند.

مثالی در اینجا ارائه شده است:

import {openDB} from 'idb';

async function createIndexesInStores () {
  const dbPromise = await openDB('test-db3', 1, {
    upgrade (db) {
      if (!db.objectStoreNames.contains('people')) {
        const peopleObjectStore = db.createObjectStore('people', { keyPath: 'email' });

        peopleObjectStore.createIndex('gender', 'gender', { unique: false });
        peopleObjectStore.createIndex('ssn', 'ssn', { unique: true });
      }

      if (!db.objectStoreNames.contains('notes')) {
        const notesObjectStore = db.createObjectStore('notes', { autoIncrement: true });

        notesObjectStore.createIndex('title', 'title', { unique: false });
      }

      if (!db.objectStoreNames.contains('logs')) {
        const logsObjectStore = db.createObjectStore('logs', { keyPath: 'id', autoIncrement: true });
      }
    }
  });
}

createIndexesInStores();

در این مثال، مخازن شیء 'people' و 'notes' دارای نمایه هستند. برای ایجاد نمایه‌ها، ابتدا نتیجه createObjectStore() (شیء ذخیره شیء) را به متغیری اختصاص دهید تا بتوانید createIndex() را روی آن فراخوانی کنید.

نحوه کار کردن با داده‌ها

این بخش نحوه ایجاد، خواندن، به‌روزرسانی، و حذف داده‌ها را شرح می‌دهد. این عملیات‌ها همگی ناهمگام هستند و از promise استفاده می‌کنند، درحالی‌که IndexedDB API از درخواست استفاده می‌کند. این کار میانای برنامه‌سازی کاربردی را ساده می‌کند. به‌جای گوش دادن به رویدادهای راه‌اندازی‌شده توسط درخواست، می‌توانید .then() را در شیء پایگاه داده برگردانده‌شده از روش openDB() فراخوانی کنید تا تعامل با پایگاه داده را شروع کنید، یا ایجاد آن را await کنید.

همه عملیات‌های داده در IndexedDB در داخل یک تراکنش انجام می‌شود. هر عملیات فرم زیر را دارد:

  1. دریافت شیء پایگاه داده.
  2. تراکنش را در پایگاه داده باز کنید.
  3. مخزن شیء را در تراکنش باز کنید.
  4. اجرای عملیات در فضای ذخیره‌سازی شیء.

تراکنش را می‌توان به‌عنوان یک پوشش امن در اطراف یک عملیات یا گروهی از عملیات‌ها درنظر گرفت. اگر یکی از کنش‌های درون تراکنش ناموفق باشد، همه کنش‌ها به حالت قبلی برمی‌گردند. تراکنش‌ها مختص یک یا چند فروشگاه شیء هستند، که هنگام باز کردن تراکنش تعریف می‌کنید. این دسترسی‌ها می‌تواند فقط خواندنی یا خواندن و نوشتن باشد. این نشان می‌دهد که آیا عملیات داخل تراکنش داده‌ها را می‌خواند یا تغییری در پایگاه داده ایجاد می‌کند.

ایجاد داده‌ها

برای ایجاد داده، روش add() را در نمونه پایگاه داده فراخوانی کنید و داده‌هایی را که می‌خواهید اضافه کنید ارسال کنید. اولین آرگومان روش add() مخزن شیئی است که می‌خواهید داده‌ها را به آن اضافه کنید، و آرگومان دوم شیئی است که حاوی فیلدها و داده‌های مرتبطی است که می‌خواهید اضافه کنید. در اینجا ساده‌ترین مثال را می‌بینید که در آن یک ردیف داده اضافه شده است:

import {openDB} from 'idb';

async function addItemToStore () {
  const db = await openDB('example-database', 1);

  await db.add('storeName', {
    field: 'data'
  });
}

addItemToStore();

هر تماس add() در یک تراکنش اتفاق می‌افتد، بنابراین حتی اگر وعده با موفقیت حل شود، لزوماً به این معنی نیست که عملیات کار کرده است. برای اطمینان از اینکه عملیات افزودن انجام شده است، باید بررسی کنید که آیا کل تراکنش بااستفاده از روش transaction.done() تکمیل شده است یا نه. این یک promise است که وقتی تراکنش تکمیل می‌شود حل می‌شود و اگر تراکنش خطا داشته باشد رد می‌شود. باید این بررسی را برای همه عملیات «نوشتن» انجام دهید، زیرا این تنها راهی است که می‌توانید متوجه شوید تغییرات در پایگاه داده واقعاً اتفاق افتاده است.

کد زیر استفاده از روش add() را در یک تراکنش نشان می‌دهد:

import {openDB} from 'idb';

async function addItemsToStore () {
  const db = await openDB('test-db4', 1, {
    upgrade (db) {
      if (!db.objectStoreNames.contains('foods')) {
        db.createObjectStore('foods', { keyPath: 'name' });
      }
    }
  });
  
  // Create a transaction on the 'foods' store in read/write mode:
  const tx = db.transaction('foods', 'readwrite');

  // Add multiple items to the 'foods' store in a single transaction:
  await Promise.all([
    tx.store.add({
      name: 'Sandwich',
      price: 4.99,
      description: 'A very tasty sandwich!',
      created: new Date().getTime(),
    }),
    tx.store.add({
      name: 'Eggs',
      price: 2.99,
      description: 'Some nice eggs you can cook up!',
      created: new Date().getTime(),
    }),
    tx.done
  ]);
}

addItemsToStore();

وقتی پایگاه داده را باز کردید (و درصورت نیاز، یک فروشگاه شیء ایجاد کردید)، باید با فراخوانی روش transaction() در آن، تراکنشی را باز کنید. این روش یک آرگومان برای فروشگاهی که می‌خواهید در آن تراکنش انجام دهید و همچنین حالت را می‌گیرد. در این مورد، ما علاقه‌مند به نوشتن در فروشگاه هستیم، بنابراین این مثال 'readwrite' را مشخص می‌کند.

مرحله بعدی این است که به‌عنوان بخشی از تراکنش، افزودن موارد به فروشگاه را شروع کنید. در مثال قبلی، ما با سه عملیات در فروشگاه 'foods' سروکار داریم که هرکدام یک promise برمی‌گردانند:

  1. افزودن رکوردی برای یک ساندویچ خوشمزه.
  2. افزودن سابقه برای چند تخم‌مرغ.
  3. نشان دادن اینکه تراکنش تکمیل شده است (tx.done).

چون همه این کنش‌ها مبتنی بر وعده هستند، باید منتظر بمانیم تا همه آن‌ها به‌پایان برسند. انتقال این وعده‌ها به Promise.all روشی خوب و ارگونومیک برای انجام این کار است. Promise.all آرایه‌ای از قول‌ها را می‌پذیرد و وقتی همه قول‌های ارسال‌شده به آن حل‌وفصل شوند، به‌پایان می‌رسد.

برای دو رکوردی که اضافه می‌شود، میانای نمونه تراکنش store add() را فراخوانی می‌کند و داده‌ها را به آن ارسال می‌کند. می‌توانید تماس Promise.all را await کنید تا وقتی تراکنش تکمیل شد، تماس تمام شود.

خواندن داده‌ها

برای خواندن داده‌ها، روش get() را در نمونه پایگاه داده‌ای که بااستفاده از روش openDB() بازیابی می‌کنید فراخوانی کنید. get() نام فروشگاه و مقدار کلید اصلی شیء موردنظر برای بازیابی را می‌گیرد. در اینجا مثالی ساده آورده شده است:

import {openDB} from 'idb';

async function getItemFromStore () {
  const db = await openDB('example-database', 1);

  // Get a value from the object store by its primary key value:
  const value = await db.get('storeName', 'unique-primary-key-value');
}

getItemFromStore();

همانند add()، روش get() یک promise برمی‌گرداند، بنابراین اگر ترجیح می‌دهید می‌توانید آن را await کنید، یا از callback مربوط به promise .then() استفاده کنید.

مثال زیر از روش get() در مخزن شیء 'foods' پایگاه داده 'test-db4' برای دریافت یک ردیف تکی با کلید اصلی 'name' استفاده می‌کند:

import {openDB} from 'idb';

async function getItemFromStore () {
  const db = await openDB('test-db4', 1);
  const value = await db.get('foods', 'Sandwich');

  console.dir(value);
}

getItemFromStore();

بازیابی یک ردیف از پایگاه داده نسبتاً ساده است: پایگاه داده را باز کنید و فضای ذخیره‌سازی شیء و مقدار کلید اصلی ردیفی را که می‌خواهید داده‌ها را از آن دریافت کنید، مشخص کنید. چون روش get() یک promise برمی‌گرداند، می‌توانید آن را await کنید.

به‌روزرسانی داده‌ها

برای به‌روزرسانی داده‌ها، روش put() را در فروشگاه شیء فراخوانی کنید. روش put() شبیه روش add() است و می‌تواند به‌جای add() برای ایجاد داده استفاده شود. در اینجا نمونه‌ای ساده از استفاده از put() برای به‌روزرسانی ردیفی در مخزن شیء براساس مقدار کلید اصلی آن آورده شده است:

import {openDB} from 'idb';

async function updateItemInStore () {
  const db = await openDB('example-database', 1);

  // Update a value from in an object store with an inline key:
  await db.put('storeName', { inlineKeyName: 'newValue' });

  // Update a value from in an object store with an out-of-line key.
  // In this case, the out-of-line key value is 1, which is the
  // auto-incremented value.
  await db.put('otherStoreName', { field: 'value' }, 1);
}

updateItemInStore();

این روش مانند روش‌های دیگر، یک promise برمی‌گرداند. همچنین می‌توانید از put() به‌عنوان بخشی از تراکنش استفاده کنید. در اینجا مثالی بااستفاده از فروشگاه 'foods' از قبل آورده شده است که قیمت ساندویچ و تخم‌مرغ را به‌روز می‌کند:

import {openDB} from 'idb';

async function updateItemsInStore () {
  const db = await openDB('test-db4', 1);
  
  // Create a transaction on the 'foods' store in read/write mode:
  const tx = db.transaction('foods', 'readwrite');

  // Update multiple items in the 'foods' store in a single transaction:
  await Promise.all([
    tx.store.put({
      name: 'Sandwich',
      price: 5.99,
      description: 'A MORE tasty sandwich!',
      updated: new Date().getTime() // This creates a new field
    }),
    tx.store.put({
      name: 'Eggs',
      price: 3.99,
      description: 'Some even NICER eggs you can cook up!',
      updated: new Date().getTime() // This creates a new field
    }),
    tx.done
  ]);
}

updateItemsInStore();

نحوه به‌روزرسانی موارد به نحوه تنظیم کلید بستگی دارد. اگر keyPath تنظیم کنید، هر ردیف در مخزن شیء با یک کلید درون‌خطی مرتبط می‌شود. مثال قبلی ردیف‌ها را براساس این کلید به‌روز می‌کند و وقتی ردیف‌ها را در این وضعیت به‌روز می‌کنید، باید آن کلید را برای به‌روز کردن مورد مناسب در مخزن شیء مشخص کنید. همچنین می‌توانید با تنظیم autoIncrement به‌عنوان کلید اصلی، کلید خارج از خط ایجاد کنید.

حذف داده‌ها

برای حذف داده‌ها، روش delete() را در مخزن شیء فراخوانی کنید:

import {openDB} from 'idb';

async function deleteItemFromStore () {
  const db = await openDB('example-database', 1);

  // Delete a value 
  await db.delete('storeName', 'primary-key-value');
}

deleteItemFromStore();

مانند add() و put()، می‌توانید از این به‌عنوان بخشی از تراکنش استفاده کنید:

import {openDB} from 'idb';

async function deleteItemsFromStore () {
  const db = await openDB('test-db4', 1);
  
  // Create a transaction on the 'foods' store in read/write mode:
  const tx = db.transaction('foods', 'readwrite');

  // Delete multiple items from the 'foods' store in a single transaction:
  await Promise.all([
    tx.store.delete('Sandwich'),
    tx.store.delete('Eggs'),
    tx.done
  ]);
}

deleteItemsFromStore();

ساختار تعامل پایگاه داده مانند سایر عملیات است. فراموش نکنید که با گنجاندن روش tx.done در آرایه‌ای که به Promise.all ارسال می‌کنید، بررسی کنید که کل تراکنش تکمیل شده باشد.

دریافت همه داده‌ها

تاکنون فقط اشیاء را یکی‌یکی از فروشگاه بازیابی کرده‌اید. همچنین می‌توانید همه داده‌ها یا زیرمجموعه‌ای از آن‌ها را از فروشگاه شیء یا نمایه بااستفاده از روش getAll() یا مکان‌نماها بازیابی کنید.

روش getAll()

ساده‌ترین راه برای بازیابی همه داده‌های ذخیره‌شده شیء، فراخوانی getAll() در ذخیره‌شده شیء یا نمایه است، مانند این:

import {openDB} from 'idb';

async function getAllItemsFromStore () {
  const db = await openDB('test-db4', 1);

  // Get all values from the designated object store:
  const allValues = await db.getAll('storeName');

  console.dir(allValues);
}

getAllItemsFromStore();

این روش همه اشیاء را در مخزن اشیاء بدون هیچ‌گونه محدودیتی برمی‌گرداند. این مستقیم‌ترین راه برای دریافت همه مقادیر از مخزن شیء است، اما کمترین انعطاف‌پذیری را نیز دارد.

import {openDB} from 'idb';

async function getAllItemsFromStore () {
  const db = await openDB('test-db4', 1);

  // Get all values from the designated object store:
  const allValues = await db.getAll('foods');

  console.dir(allValues);
}

getAllItemsFromStore();

این مثال getAll() را در مخزن شیء 'foods' فراخوانی می‌کند. این کنش همه اشیای 'foods' را که براساس کلید اصلی مرتب شده‌اند برمی‌گرداند.

نحوه استفاده کردن از مکان‌نماها

مکان‌نماها روشی انعطاف‌پذیرتر برای بازیابی چند شیء هستند. مکان‌نما هر شیء را در مخزن شیء یا نمایه به‌صورت تک‌تک انتخاب می‌کند و به شما امکان می‌دهد وقتی داده‌ها انتخاب شدند، کاری با آن‌ها انجام دهید. مکان‌نماها، مانند سایر عملیات پایگاه داده، در تراکنش‌ها کار می‌کنند.

برای ایجاد مکان‌نما، openCursor() را در فروشگاه شیء به‌عنوان بخشی از تراکنش فراخوانی کنید. بااستفاده از فروشگاه 'foods' از مثال‌های قبلی، این نحوه پیش بردن مکان‌نما در تمام ردیف‌های داده در یک فروشگاه شیء است:

import {openDB} from 'idb';

async function getAllItemsFromStoreWithCursor () {
  const db = await openDB('test-db4', 1);
  const tx = await db.transaction('foods', 'readonly');

  // Open a cursor on the designated object store:
  let cursor = await tx.store.openCursor();

  // Iterate on the cursor, row by row:
  while (cursor) {
    // Show the data in the row at the current cursor position:
    console.log(cursor.key, cursor.value);

    // Advance the cursor to the next row:
    cursor = await cursor.continue();
  }
}

getAllItemsFromStoreWithCursor();

تراکنش در این مورد در حالت 'readonly' باز می‌شود و روش openCursor آن فراخوانی می‌شود. در حلقه while بعدی، ویژگی‌های key و value ردیف در موقعیت فعلی مکان‌نما قابل‌خواندن است و می‌توانید روی این مقادیر به هر روشی که برای برنامه‌تان منطقی‌تر است کار کنید. وقتی آماده بودید، می‌توانید متد continue() شیء cursor را فراخوانی کنید تا به ردیف بعدی بروید و وقتی مکان‌نما به انتهای مجموعه داده رسید، حلقه while پایان می‌یابد.

استفاده از مکان‌نما با محدوده‌ها و نمایه‌ها

نمایه‌ها به شما امکان می‌دهند داده‌ها را در مخزن شیء با ویژگی‌ای غیراز کلید اصلی واکشی کنید. می‌توانید نمایه‌ای روی هر ویژگی ایجاد کنید که به keyPath برای نمایه تبدیل می‌شود، محدوده‌ای را روی آن ویژگی مشخص کنید، و داده‌های درون محدوده را بااستفاده از getAll() یا مکان‌نما دریافت کنید.

محدوده خود را بااستفاده از شیء IDBKeyRange و هریک از روش‌های زیر تعریف کنید:

روش‌های upperBound() و lowerBound() حدود بالا و پایین محدوده را مشخص می‌کنند.

IDBKeyRange.lowerBound(indexKey);

یا:

IDBKeyRange.upperBound(indexKey);

هرکدام یک آرگومان می‌گیرند: مقدار keyPath نمایه برای موردی که می‌خواهید به‌عنوان حد بالا یا پایین مشخص کنید.

روش bound() هم حد بالا و هم حد پایین را مشخص می‌کند:

IDBKeyRange.bound(lowerIndexKey, upperIndexKey);

محدوده این توابع به‌طور پیش‌فرض شامل است، یعنی شامل داده‌هایی است که به‌عنوان حدود محدوده مشخص شده‌اند. برای حذف این مقادیر، محدوده را به‌عنوان انحصاری با ارسال true به‌عنوان آرگومان دوم برای lowerBound() یا upperBound()، یا به‌عنوان آرگومان سوم و چهارم bound()، به‌ترتیب برای حدود پایین و بالا مشخص کنید.

مثال بعدی از نمایه‌ای در ویژگی 'price' در مخزن شیء 'foods' استفاده می‌کند. اکنون فروشگاه فرمی نیز دارد که با دو ورودی برای حدود بالا و پایین محدوده به آن پیوست شده است. از کد زیر برای پیدا کردن غذاهایی استفاده کنید که قیمت آن‌ها بین این حدود است:

import {openDB} from 'idb';

async function searchItems (lower, upper) {
  if (!lower === '' && upper === '') {
    return;
  }

  let range;

  if (lower !== '' && upper !== '') {
    range = IDBKeyRange.bound(lower, upper);
  } else if (lower === '') {
    range = IDBKeyRange.upperBound(upper);
  } else {
    range = IDBKeyRange.lowerBound(lower);
  }

  const db = await openDB('test-db4', 1);
  const tx = await db.transaction('foods', 'readonly');
  const index = tx.store.index('price');

  // Open a cursor on the designated object store:
  let cursor = await index.openCursor(range);

  if (!cursor) {
    return;
  }

  // Iterate on the cursor, row by row:
  while (cursor) {
    // Show the data in the row at the current cursor position:
    console.log(cursor.key, cursor.value);

    // Advance the cursor to the next row:
    cursor = await cursor.continue();
  }
}

// Get items priced between one and four dollars:
searchItems(1.00, 4.00);

کد نمونه ابتدا مقادیر محدودیت‌ها را دریافت می‌کند و بررسی می‌کند که آیا محدودیت‌ها وجود دارند یا نه. بلوک بعدی کد تصمیم می‌گیرد که از کدام روش برای محدود کردن محدوده براساس مقادیر استفاده کند. در تعامل پایگاه داده، مخزن شیء را در تراکنش طبق معمول باز کنید، سپس نمایه 'price' را در مخزن شیء باز کنید. نمایه 'price' به شما امکان می‌دهد موارد را براساس قیمت جستجو کنید.

سپس کد مکان‌نمایی را روی نمایه باز می‌کند و در محدوده عبور می‌کند. مکان‌نما یک promise برمی‌گرداند که نشان‌دهنده اولین شیء در محدوده است، یا اگر داده‌ای در محدوده وجود نداشته باشد، undefined را برمی‌گرداند. روش cursor.continue() یک مکان‌نما را برمی‌گرداند که نشان‌دهنده شیء بعدی است و در حلقه ادامه می‌یابد تا به انتهای محدوده برسید.

نسخه‌بندی پایگاه داده

وقتی روش openDB() را فراخوانی می‌کنید، می‌توانید شماره نسخه پایگاه داده را در پارامتر دوم مشخص کنید. در همه مثال‌های این راهنما، نسخه روی 1 تنظیم شده است، اما اگر لازم باشد پایگاه داده را به‌نحوی اصلاح کنید، می‌توانید آن را به نسخه جدیدی ارتقا دهید. اگر نسخه مشخص‌شده بالاتر از نسخه پایگاه داده موجود باشد، upgrade callback در شیء رویداد اجرا می‌شود و به شما امکان می‌دهد فروشگاه‌های شیء و نمایه‌های جدیدی به پایگاه داده اضافه کنید.

شیء db در فراخوانی برگشتی upgrade دارای ویژگی خاص oldVersion است، که شماره نسخه پایگاه داده‌ای را که مرورگر به آن دسترسی دارد نشان می‌دهد. می‌توانید این شماره نسخه را به عبارت switch ارسال کنید تا بلوک‌های کد را در فراخوانی برگشتی upgrade براساس شماره نسخه پایگاه داده موجود اجرا کنید. مثالی در اینجا ارائه شده است:

import {openDB} from 'idb';

const db = await openDB('example-database', 2, {
  upgrade (db, oldVersion) {
    switch (oldVersion) {
      case 0:
        // Create first object store:
        db.createObjectStore('store', { keyPath: 'name' });

      case 1:
        // Get the original object store, and create an index on it:
        const tx = await db.transaction('store', 'readwrite');
        tx.store.createIndex('name', 'name');
    }
  }
});

این مثال جدیدترین نسخه پایگاه داده را روی 2 تنظیم می‌کند. وقتی این کد برای اولین‌بار اجرا می‌شود، پایگاه داده هنوز در مرورگر وجود ندارد، بنابراین oldVersion 0 است و عبارت switch در case 0 شروع می‌شود. در این مثال، این کار یک مخزن شیء 'store' به پایگاه داده اضافه می‌کند.

نکته کلیدی: در دستورهای switch، معمولاً بعداز هر بلوک case یک break وجود دارد، اما در اینجا عمداً از آن استفاده نشده است. به‌این‌ترتیب، اگر پایگاه داده موجود چند نسخه قدیمی‌تر باشد یا وجود نداشته باشد، کد در بقیه بلوک‌های case ادامه می‌یابد تا به‌روز شود. بنابراین در این مثال، مرورگر به اجرای case 1 ادامه می‌دهد و نمایه name را در مخزن شیء store ایجاد می‌کند.

برای ایجاد نمایه 'description' در فضای ذخیره‌سازی شیء 'store'، شماره نسخه را به‌روز کنید و بلوک case جدیدی به‌صورت زیر اضافه کنید:

import {openDB} from 'idb';

const db = await openDB('example-database', 3, {
  upgrade (db, oldVersion) {
    switch (oldVersion) {
      case 0:
        // Create first object store:
        db.createObjectStore('store', { keyPath: 'name' });

      case 1:
        // Get the original object store, and create an index on it:
        const tx = await db.transaction('store', 'readwrite');
        tx.store.createIndex('name', 'name');

      case 2:
        const tx = await db.transaction('store', 'readwrite');
        tx.store.createIndex('description', 'description');
    }
  }
});

اگر پایگاه داده‌ای که در مثال قبلی ایجاد کردید هنوز در مرورگر وجود داشته باشد، وقتی این کد اجرا می‌شود، oldVersion برابر با 2 است. مرورگر از case 0 و case 1 رد می‌شود و کد را در case 2 اجرا می‌کند که نمایه description را ایجاد می‌کند. پس‌از آن، مرورگر پایگاه داده‌ای در نسخه ۳ دارد که حاوی یک فروشگاه شیء store با نمایه‌های name و description است.

مطالعه بیشتر

منابع زیر اطلاعات و زمینه بیشتری برای استفاده از IndexedDB ارائه می‌دهند.

سند IndexedDB

محدودیت‌های ذخیره‌سازی داده‌ها