IndexedDB ile çalışma

Bu kılavuzda, IndexedDB API ile ilgili temel bilgiler ele alınmaktadır. Jake Archibald'ın IndexedDB Promised kitaplığını kullanıyoruz. Bu kitaplık, IndexedDB API'ye çok benzer ancak daha kısa bir söz dizimi için await yapabileceğiniz promise'leri kullanır. Bu sayede API'nin yapısı korunurken basitleştirilmesi sağlanır.

IndexedDB nedir?

IndexedDB, kullanıcının tarayıcısında hemen her şeyin depolanmasına olanak tanıyan büyük ölçekli bir NoSQL depolama sistemidir. IndexedDB, her zamanki arama, alma ve yerleştirme işlemlerinin yanı sıra işlemleri de destekler ve büyük miktarda yapılandırılmış veriyi depolamak için idealdir.

Her IndexedDB veritabanı bir kaynağa (genellikle site alanı veya alt alanı) özgüdür. Yani başka bir kaynağa erişemez veya başka bir kaynak tarafından erişilemez. Veri depolama sınırları genellikle geniştir ancak farklı tarayıcılar, sınırları ve veri silme işlemlerini farklı şekilde yönetir. Daha fazla bilgi için Daha fazla bilgi bölümüne bakın.

IndexedDB terimleri

Database
IndexedDB'nin en yüksek düzeyi. Bu veritabanı, kalıcı hale getirmek istediğiniz verileri içeren nesne depolarını barındırır. İstediğiniz adlarla birden fazla veritabanı oluşturabilirsiniz.
Nesne deposu
İlişkisel veritabanlarındaki tablolara benzer şekilde, verileri depolamak için kullanılan ayrı bir paket. Genellikle, depoladığınız her veri türü (JavaScript veri türü değil) için bir nesne deposu vardır. Veritabanı tablolarının aksine, bir depodaki verilerin JavaScript veri türlerinin tutarlı olması gerekmez. Örneğin, bir uygulamada üç kişi hakkında bilgi içeren bir people nesne deposu varsa bu kişilerin yaş özellikleri 53, 'twenty-five' ve unknown olabilir.
Dizin
Başka bir nesne deposundaki (referans nesne deposu olarak adlandırılır) verileri, verilerin ayrı bir özelliğiyle düzenlemek için kullanılan bir tür nesne deposu. Dizin, nesne deposundaki kayıtları bu özelliğe göre almak için kullanılır. Örneğin, kişileri depoluyorsanız daha sonra bu kişileri adlarına, yaşlarına veya en sevdikleri hayvana göre getirmek isteyebilirsiniz.
İşlem
Veritabanıyla etkileşim.
İşlem
Veritabanı bütünlüğünü sağlayan bir işlem veya işlem grubunu sarmalayan bir yapı. Bir işlemdeki eylemlerden biri başarısız olursa hiçbiri uygulanmaz ve veritabanı, işlem başlamadan önceki durumuna döner. IndexedDB'deki tüm okuma veya yazma işlemleri bir işlemin parçası olmalıdır. Bu sayede, veritabanında aynı anda işlem yapan diğer iş parçacıklarıyla çakışma riski olmadan atomik okuma-değiştirme-yazma işlemleri yapılabilir.
İmleç
Veritabanındaki birden fazla kayıt üzerinde yineleme yapma mekanizması.

IndexedDB desteği nasıl kontrol edilir?

IndexedDB neredeyse her yerde desteklenir. Ancak eski tarayıcılarla çalışıyorsanız her ihtimale karşı desteklenen özellikleri tespit etmek iyi bir fikir olabilir. Bunu yapmanın en kolay yolu window nesnesini kontrol etmektir:

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

Veritabanı açma

IndexedDB ile istediğiniz adlarla birden fazla veritabanı oluşturabilirsiniz. Açmaya çalıştığınız veritabanı mevcut değilse otomatik olarak oluşturulur. Bir veritabanını açmak için idb kitaplığındaki openDB() yöntemini kullanın:

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

Bu yöntem, bir veritabanı nesnesine çözümlenen bir promise döndürür. Veritabanını oluşturmak için openDB() yöntemini kullanırken bir ad, sürüm numarası ve etkinlik nesnesi sağlayın.

openDB() yönteminin bağlam içindeki bir örneğini aşağıda bulabilirsiniz:

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 desteği kontrolünü anonim işlevin en üstüne yerleştirin. Bu, tarayıcı IndexedDB'yi desteklemiyorsa işlevden çıkar. İşlev devam edebiliyorsa 'test-db1' adlı bir veritabanını açmak için openDB() yöntemini çağırır. Bu örnekte, basitlik açısından isteğe bağlı events nesnesi çıkarılmıştır ancak IndexedDB ile anlamlı bir çalışma yapmak için bu nesneyi belirtmeniz gerekir.

Nesne depolarıyla çalışma

IndexedDB veritabanı, her birinde anahtar için bir sütun ve bu anahtarla ilişkili veriler için başka bir sütun bulunan bir veya daha fazla nesne deposu içerir.

Nesne depoları oluşturma

İyi yapılandırılmış bir IndexedDB veritabanında, kalıcı olması gereken her veri türü için bir nesne deposu bulunmalıdır. Örneğin, kullanıcı profillerini ve notlarını saklayan bir sitede person nesnelerini içeren bir people nesne deposu ve note nesnelerini içeren bir notes nesne deposu olabilir.

Veritabanı bütünlüğünü sağlamak için nesne depolarını yalnızca bir openDB() çağrısındaki etkinlik nesnesinde oluşturabilir veya kaldırabilirsiniz. Etkinlikler nesnesi, nesne depoları oluşturmanıza olanak tanıyan bir upgrade() yöntemini gösterir. Nesne deposunu oluşturmak için upgrade() yönteminin içindeki createObjectStore() yöntemini çağırın:

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

Bu yöntem, nesne deposunun adını ve nesne deposu için çeşitli özellikleri tanımlamanıza olanak tanıyan isteğe bağlı bir yapılandırma nesnesini alır.

Aşağıda, createObjectStore() işlevinin nasıl kullanılacağına dair bir örnek verilmiştir:

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

Bu örnekte, nesne deposunu oluşturmak için openDB() yöntemine bir etkinlik nesnesi iletilir ve daha önce olduğu gibi, nesne deposunu oluşturma işlemi etkinlik nesnesinin upgrade() yönteminde yapılır. Ancak mevcut bir nesne deposu oluşturmaya çalışırsanız tarayıcı hata vereceğinden, createObjectStore() yöntemini nesne deposunun mevcut olup olmadığını kontrol eden bir if ifadesine sarmalamanızı öneririz. if bloğunun içinde, 'firstOS' adlı bir nesne deposu oluşturmak için createObjectStore() yöntemini çağırın.

Birincil anahtarları tanımlama

Nesne depolarını tanımlarken birincil anahtar kullanarak verilerin depoda nasıl benzersiz şekilde tanımlanacağını belirleyebilirsiniz. Birincil anahtarı, anahtar yolu tanımlayarak veya anahtar oluşturucu kullanarak tanımlayabilirsiniz.

Anahtar yolu, her zaman mevcut olan ve benzersiz bir değer içeren bir özelliktir. Örneğin, bir people nesne deposu söz konusu olduğunda anahtar yolu olarak e-posta adresini seçebilirsiniz:

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

Bu örnekte, 'people' adlı bir nesne deposu oluşturulur ve keyPath seçeneğinde birincil anahtar olarak email özelliği atanır.

autoIncrement gibi bir anahtar oluşturucu da kullanabilirsiniz. Anahtar oluşturucu, nesne deposuna eklenen her nesne için benzersiz bir değer oluşturur. Varsayılan olarak, bir anahtar belirtmezseniz IndexedDB bir anahtar oluşturur ve bu anahtarı verilerden ayrı olarak depolar.

Aşağıdaki örnekte, 'notes' adlı bir nesne deposu oluşturulur ve birincil anahtar, otomatik olarak artan bir sayı olarak atanacak şekilde ayarlanır:

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

Aşağıdaki örnek, önceki örneğe benzer ancak bu kez otomatik olarak artan değer, 'id' adlı bir özelliğe açıkça atanır.

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

Anahtarı tanımlamak için hangi yöntemin kullanılacağı verilerinize bağlıdır. Verilerinizde her zaman benzersiz olan bir özellik varsa bu benzersizliği zorunlu kılmak için özelliği keyPath yapabilirsiniz. Aksi takdirde, otomatik olarak artan bir değer kullanın.

Aşağıdaki kod, nesne depolarında birincil anahtarları tanımlamanın çeşitli yollarını gösteren üç nesne deposu oluşturur:

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

Dizinleri tanımlama

Dizinler, referans nesne deposundan belirtilen bir özelliğe göre veri almak için kullanılan bir tür nesne deposudur. Dizin, referans nesne deposunun içinde bulunur ve aynı verileri içerir ancak referans deposunun birincil anahtarı yerine anahtar yolu olarak belirtilen özelliği kullanır. Dizinler, nesne depolarınızı oluştururken oluşturulmalıdır ve verilerinizde benzersiz bir kısıtlama tanımlamak için kullanılabilir.

Dizin oluşturmak için bir nesne deposu örneğinde createIndex() yöntemini çağırın:

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

Bu yöntem, bir dizin nesnesi oluşturur ve döndürür. Nesne deposu örneğindeki createIndex() yöntemi, ilk bağımsız değişken olarak yeni dizinin adını alır. İkinci bağımsız değişken ise dizine eklemek istediğiniz verilerdeki özelliği ifade eder. Son bağımsız değişken, dizinin nasıl çalışacağını belirleyen iki seçenek tanımlamanıza olanak tanır: unique ve multiEntry. unique, true olarak ayarlanırsa dizin, tek bir anahtar için yinelenen değerlere izin vermez. Ardından multiEntry, dizine eklenen özellik bir dizi olduğunda createIndex() öğesinin nasıl davranacağını belirler. true olarak ayarlanırsa createIndex(), her dizi öğesi için dizine bir giriş ekler. Aksi takdirde, diziyi içeren tek bir giriş ekler.

Aşağıda bununla ilgili bir örnek verilmiştir:

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

Bu örnekte, 'people' ve 'notes' nesne depolarında dizinler vardır. Dizinleri oluşturmak için önce createObjectStore() (bir nesne deposu nesnesi) sonucunu bir değişkene atayın. Böylece bu değişkende createIndex() yöntemini çağırabilirsiniz.

Verilerle çalışma

Bu bölümde veri oluşturma, okuma, güncelleme ve silme işlemleri açıklanmaktadır. Bu işlemlerin tümü eşzamansızdır ve IndexedDB API'nin istekleri kullandığı yerlerde vaatleri kullanır. Bu sayede API daha basit hale gelir. İstek tarafından tetiklenen etkinlikleri dinlemek yerine, veritabanıyla etkileşimleri başlatmak için openDB() yönteminden döndürülen veritabanı nesnesinde .then() yöntemini çağırabilir veya oluşturulmasını await.

IndexedDB'deki tüm veri işlemleri bir işlem içinde gerçekleştirilir. Her işlemin biçimi aşağıdaki gibidir:

  1. Veritabanı nesnesini alma
  2. Veritabanında işlem açın.
  3. İşlemde nesne deposunu açın.
  4. Nesne deposunda işlem yapma.

İşlem, bir operasyon veya operasyon grubunun güvenli bir sarmalayıcısı olarak düşünülebilir. Bir işlemdeki eylemlerden biri başarısız olursa tüm eylemler geri alınır. İşlemler, işlemi açarken tanımladığınız bir veya daha fazla nesne deposuna özeldir. Salt okunur veya okuma ve yazma erişimi verebilirsiniz. Bu, işlem içindeki işlemlerin verileri okuyup okumadığını veya veritabanında değişiklik yapıp yapmadığını belirtir.

Veri oluşturma

Veri oluşturmak için veritabanı örneğinde add() yöntemini çağırın ve eklemek istediğiniz verileri iletin. add() yönteminin ilk bağımsız değişkeni, verileri eklemek istediğiniz nesne deposudur. İkinci bağımsız değişken ise eklemek istediğiniz alanları ve ilişkili verileri içeren bir nesnedir. Tek bir veri satırının eklendiği en basit örnek aşağıda verilmiştir:

import {openDB} from 'idb';

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

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

addItemToStore();

Her add() çağrısı bir işlem içinde gerçekleşir. Bu nedenle, promise başarıyla çözümlense bile bu durum, işlemin başarılı olduğu anlamına gelmez. Ekleme işleminin gerçekleştirildiğinden emin olmak için transaction.done() yöntemini kullanarak işlemin tamamının tamamlanıp tamamlanmadığını kontrol etmeniz gerekir. Bu, işlem tamamlandığında çözümlenen ve işlemde hata oluşursa reddedilen bir Promise'dir. Veritabanındaki değişikliklerin gerçekten gerçekleştiğini bilmenin tek yolu bu olduğundan, tüm "yazma" işlemleri için bu kontrolü yapmanız gerekir.

Aşağıdaki kodda, bir işlem içinde add() yönteminin kullanımı gösterilmektedir:

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

Veritabanını açtıktan (ve gerekirse bir nesne deposu oluşturduktan) sonra transaction() yöntemini çağırarak bir işlem açmanız gerekir. Bu yöntem, işlem yapmak istediğiniz mağaza ve mod için bir bağımsız değişken alır. Bu örnekte, mağazaya yazmak istediğimiz için 'readwrite' belirtiliyor.

Sonraki adım, işlem kapsamında mağazaya ürün eklemeye başlamaktır. Yukarıdaki örnekte, 'foods' deposunda her biri bir söz döndüren üç işlemle ilgileniyoruz:

  1. Lezzetli bir sandviç için kayıt ekleniyor.
  2. Yumurta için kayıt ekleniyor.
  3. İşlemin tamamlandığını bildirme (tx.done).

Tüm bu işlemler Promise tabanlı olduğundan, hepsinin tamamlanmasını beklememiz gerekir. Bu vaatleri Promise.all işlevine iletmek, bu işlemi gerçekleştirmenin iyi ve ergonomik bir yoludur. Promise.all, bir promise dizisi kabul eder ve kendisine iletilen tüm promise'ler çözümlendiğinde tamamlanır.

Eklenen iki kayıt için işlem örneğinin store arayüzü add() yöntemini çağırır ve verileri bu yönteme iletir. İşlem tamamlandığında sona ermesi için Promise.all çağrısını await.

Verileri okuma

Verileri okumak için openDB() yöntemini kullanarak aldığınız veritabanı örneğinde get() yöntemini çağırın. get(), mağazanın adını ve almak istediğiniz nesnenin birincil anahtar değerini alır. Aşağıda basit bir örnek verilmiştir:

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() gibi get() yöntemi de bir promise döndürür. Bu nedenle, isterseniz await kullanabilir veya promise'in .then() geri çağırma işlevini kullanabilirsiniz.

Aşağıdaki örnekte, 'name' birincil anahtarına göre tek bir satır almak için 'test-db4' veritabanının 'foods' nesne deposunda get() yöntemi kullanılmaktadır:

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

Veritabanından tek bir satır almak oldukça basittir: Veritabanını açın ve veri almak istediğiniz satırın nesne deposunu ve birincil anahtar değerini belirtin. get() yöntemi bir promise döndürdüğü için await yapabilirsiniz.

Verileri güncelle

Verileri güncellemek için nesne deposunda put() yöntemini çağırın. put() yöntemi, add() yöntemine benzer ve veri oluşturmak için add() yerine de kullanılabilir. Nesne deposundaki bir satırı birincil anahtar değerine göre güncellemek için put() kullanımına dair basit bir örneği aşağıda bulabilirsiniz:

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

Diğer yöntemler gibi bu yöntem de bir promise döndürür. put() özelliğini bir işlemin parçası olarak da kullanabilirsiniz. Daha önce bahsettiğimiz 'foods' mağazasını kullanarak sandviç ve yumurta fiyatını güncelleyen bir örnek:

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

Öğelerin nasıl güncelleneceği, anahtarı nasıl ayarladığınıza bağlıdır. keyPath ayarlarsanız nesne deposundaki her satır bir satır içi anahtarla ilişkilendirilir. Yukarıdaki örnekte, satırlar bu anahtara göre güncellenir. Bu durumda satırları güncellerken nesne deposundaki uygun öğeyi güncellemek için bu anahtarı belirtmeniz gerekir. Birincil anahtar olarak autoIncrement ayarlayarak satır dışı anahtar da oluşturabilirsiniz.

Verileri sil

Verileri silmek için nesne deposunda delete() yöntemini çağırın:

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() ve put() gibi, bunu da bir işlemin parçası olarak kullanabilirsiniz:

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

Veritabanı etkileşiminin yapısı, diğer işlemlerle aynıdır. Promise.all yöntemine ilettiğiniz diziye tx.done yöntemini ekleyerek tüm işlemin tamamlandığını kontrol etmeyi unutmayın.

Tüm veriler alınıyor

Şimdiye kadar depodan nesneleri tek tek aldınız. Ayrıca getAll() yöntemini veya imleçleri kullanarak bir nesne deposundan ya da dizinden tüm verileri veya bir alt kümeyi alabilirsiniz.

getAll() yöntemi

Bir nesne deposunun tüm verilerini almanın en basit yolu, nesne deposunda veya dizinde getAll() yöntemini çağırmaktır:

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

Bu yöntem, nesne deposundaki tüm nesneleri herhangi bir kısıtlama olmadan döndürür. Bu, bir nesne deposundaki tüm değerleri almanın en doğrudan yoludur ancak aynı zamanda en az esnek olanıdır.

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

Bu örnek, 'foods' nesne deposunda getAll() yöntemini çağırır. Bu işlem, 'foods' tablosundaki tüm nesneleri birincil anahtara göre sıralanmış olarak döndürür.

İmleçleri kullanma

İmleçler, birden fazla nesneyi almanın daha esnek bir yoludur. İmleç, bir nesne deposundaki veya dizindeki her nesneyi tek tek seçerek seçilen verilerle işlem yapmanıza olanak tanır. İmleçler, diğer veritabanı işlemleri gibi işlemlerde çalışır.

İmleç oluşturmak için bir işlemin parçası olarak nesne deposunda openCursor() yöntemini çağırın. Önceki örneklerdeki 'foods' deposunu kullanarak bir imleci nesne deposundaki tüm veri satırlarında nasıl ilerleteceğinizi görebilirsiniz:

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

Bu durumda işlem 'readonly' modunda açılır ve openCursor yöntemi çağrılır. Sonraki bir while döngüsünde, imlecin geçerli konumundaki satırın key ve value özellikleri okunabilir ve bu değerler üzerinde uygulamanız için en mantıklı şekilde işlem yapabilirsiniz. Hazır olduğunuzda, sonraki satıra gitmek için cursor nesnesinin continue() yöntemini çağırabilirsiniz. İmleç, veri kümesinin sonuna ulaştığında while döngüsü sona erer.

Aralıklar ve dizinlerle imleçleri kullanma

Dizinler, bir nesne deposundaki verileri birincil anahtar dışındaki bir özellik aracılığıyla getirmenize olanak tanır. Herhangi bir mülkte dizin oluşturabilirsiniz. Bu mülk, dizinin keyPath olur. Bu mülkte bir aralık belirtebilir ve getAll() veya imleç kullanarak aralıktaki verileri alabilirsiniz.

IDBKeyRange nesnesini ve aşağıdaki yöntemlerden herhangi birini kullanarak aralığınızı tanımlayın:

upperBound() ve lowerBound() yöntemleri, aralığın üst ve alt sınırlarını belirtir.

IDBKeyRange.lowerBound(indexKey);

veya:

IDBKeyRange.upperBound(indexKey);

Her biri bir bağımsız değişken alır: üst veya alt sınır olarak belirtmek istediğiniz öğenin dizininin keyPath değeri.

bound() yöntemi hem üst hem de alt sınır belirtir:

IDBKeyRange.bound(lowerIndexKey, upperIndexKey);

Bu işlevlerin aralığı varsayılan olarak kapsayıcıdır. Yani aralığın sınırları olarak belirtilen verileri içerir. Bu değerleri hariç tutmak için lowerBound() veya upperBound() işlevinin ikinci bağımsız değişkeni olarak ya da bound() işlevinin sırasıyla alt ve üst sınırlar için üçüncü ve dördüncü bağımsız değişkenleri olarak true değerini ileterek aralığı hariç tutulan olarak belirtin.

Sonraki örnekte, 'foods' nesne deposundaki 'price' özelliğinde bir dizin kullanılır. Mağazada artık aralığın üst ve alt sınırları için iki giriş içeren bir form da var. Fiyatları bu sınırlar arasında olan yiyecekleri bulmak için aşağıdaki kodu kullanın:

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

Örnek kod, önce sınırların değerlerini alır ve sınırların mevcut olup olmadığını kontrol eder. Bir sonraki kod bloğu, değerlere göre aralığı sınırlamak için hangi yöntemin kullanılacağına karar verir. Veritabanı etkileşiminde, işlemdeki nesne deposunu her zamanki gibi açın, ardından nesne deposundaki 'price' dizinini açın. 'price' dizini, öğeleri fiyata göre aramanıza olanak tanır.

Ardından kod, dizinde bir imleç açar ve aralığı iletir. İmleç, aralıktaki ilk nesneyi temsil eden bir promise döndürür. Aralıkta veri yoksa undefined döndürülür. cursor.continue() yöntemi, bir sonraki nesneyi temsil eden bir imleç döndürür ve aralığın sonuna ulaşana kadar döngü boyunca devam eder.

Veritabanı sürümü oluşturma

openDB() yöntemini çağırırken ikinci parametrede veritabanı sürüm numarasını belirtebilirsiniz. Bu kılavuzdaki tüm örneklerde sürüm 1 olarak ayarlanmıştır ancak veritabanını bir şekilde değiştirmeniz gerekiyorsa yeni bir sürüme yükseltebilirsiniz. Belirtilen sürüm, mevcut veritabanının sürümünden daha yüksekse etkinlik nesnesindeki upgrade geri çağırma işlevi yürütülür. Bu sayede veritabanına yeni nesne depoları ve dizinler ekleyebilirsiniz.

upgrade geri çağırmasındaki db nesnesinin özel bir oldVersion özelliği vardır. Bu özellik, tarayıcının erişebildiği veritabanının sürüm numarasını gösterir. Mevcut veritabanı sürüm numarasına göre upgrade geri çağırma işlevindeki kod bloklarını yürütmek için bu sürüm numarasını bir switch ifadesine iletebilirsiniz. Aşağıda bununla ilgili bir örnek verilmiştir:

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

Bu örnekte, veritabanının en yeni sürümü 2 olarak ayarlanır. Bu kod ilk kez yürütüldüğünde veritabanı henüz tarayıcıda mevcut olmadığından oldVersion, 0 olur ve switch ifadesi case 0 ile başlar. Örnekte, bu işlem veritabanına bir 'store' nesne deposu ekler.

Önemli nokta: switch ifadelerinde genellikle her case bloğundan sonra bir break bulunur ancak burada kasıtlı olarak kullanılmamıştır. Bu sayede, mevcut veritabanı birkaç sürüm gerideyse veya mevcut değilse kod, güncel olana kadar case bloklarının geri kalanında devam eder. Örnekte tarayıcı, case 1 üzerinden yürütmeye devam ederek store nesne deposunda name dizini oluşturur.

'store' nesne deposunda 'description' dizini oluşturmak için sürüm numarasını güncelleyin ve aşağıdaki gibi yeni bir case bloğu ekleyin:

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

Önceki örnekte oluşturduğunuz veritabanı tarayıcıda hâlâ mevcutsa bu kod yürütüldüğünde oldVersion, 2 olur. Tarayıcı, case 0 ve case 1 öğelerini atlar ve case 2 içindeki kodu yürütür. Bu kod, description dizini oluşturur. Ardından tarayıcıda, name ve description dizinlerini içeren bir store nesne deposuna sahip 3. sürüm bir veritabanı bulunur.

Daha fazla bilgi

Aşağıdaki kaynaklarda IndexedDB kullanımıyla ilgili daha fazla bilgi ve bağlam sunulmaktadır.

IndexedDB belgeleri

Veri depolama sınırları