Работа с IndexedDB,Работа с IndexedDB

В этом руководстве рассматриваются основы API IndexedDB . Мы используем библиотеку Promized IndexedDB Джейка Арчибальда, которая очень похожа на API IndexedDB, но использует промисы, поэтому вы можете await более краткого синтаксиса. Это упрощает API, сохраняя при этом его структуру.

Что такое IndexedDB?

IndexedDB — это крупномасштабная система хранения NoSQL, которая позволяет хранить практически все что угодно в браузере пользователя. Помимо обычных действий поиска, получения и размещения, IndexedDB также поддерживает транзакции и хорошо подходит для хранения больших объемов структурированных данных.

Каждая база данных IndexedDB уникальна для источника (обычно домена или поддомена сайта), что означает, что к ней не может получить доступ ни один другой источник. Ограничения на хранение данных обычно велики, если они вообще существуют, но разные браузеры по-разному обрабатывают ограничения и удаление данных. Дополнительную информацию смотрите в разделе «Дополнительная литература» .

Условия индексированной базы данных

База данных
Самый высокий уровень IndexedDB. Он содержит хранилища объектов, которые, в свою очередь, содержат данные, которые вы хотите сохранить. Вы можете создать несколько баз данных с любыми именами по вашему выбору.
Хранилище объектов
Отдельный сегмент для хранения данных, аналогичный таблицам в реляционных базах данных. Обычно для каждого типа сохраняемых данных (не типа данных JavaScript) существует одно хранилище объектов. В отличие от таблиц базы данных, типы данных JavaScript в хранилище не обязательно должны быть согласованными. Например, если в приложении есть хранилище объектов 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();

Этот метод возвращает обещание, которое разрешается в объект базы данных. При использовании метода 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() .

Как работать с данными

В этом разделе описывается, как создавать, читать, обновлять и удалять данные. Все эти операции являются асинхронными и используют обещания, тогда как API 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() . Это обещание, которое выполняется, когда транзакция завершается сама по себе, и отклоняется, если транзакция ошибочна. Вы должны выполнить эту проверку для всех операций «записи», поскольку это единственный способ узнать, действительно ли произошли изменения в базе данных.

Следующий код демонстрирует использование метода 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' , каждая из которых возвращает обещание:

  1. Добавляю запись на вкусный бутерброд.
  2. Добавляю запись на несколько яиц.
  3. Сигнализация о завершении транзакции ( tx.done ).

Поскольку все эти действия основаны на обещаниях, нам нужно дождаться их завершения. Передача этих обещаний в Promise.all — хороший и эргономичный способ сделать это. Promise.all принимает массив промисов и завершается, когда все переданные ему промисы будут решены.

Для двух добавляемых записей интерфейс store экземпляра транзакции вызывает add() и передает ему данные. Вы можете await вызова Promise.all , чтобы он завершился после завершения транзакции.

Чтение данных

Чтобы прочитать данные, вызовите метод 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() возвращает обещание, поэтому вы можете await его, если хотите, или использовать обратный вызов обещания .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() возвращает обещание, вы можете 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();

Как и другие методы, этот метод возвращает обещание. Вы также можете использовать 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 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' позволяет искать товары по цене.

Затем код открывает курсор по индексу и передает диапазон. Курсор возвращает обещание, представляющее первый объект в диапазоне, или undefined , если в диапазоне нет данных. Метод cursor.continue() возвращает курсор, представляющий следующий объект, и продолжает цикл, пока не достигнет конца диапазона.

Управление версиями базы данных

Когда вы вызываете метод openDB() , вы можете указать номер версии базы данных во втором параметре. Во всех примерах в этом руководстве установлена ​​версия 1 , но базу данных можно обновить до новой версии, если вам нужно каким-либо образом изменить ее. Если указанная версия больше версии существующей базы данных, в объекте события выполняется обратный вызов upgrade , позволяющий добавлять в базу данных новые хранилища объектов и индексы.

Объект 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 . После этого в браузере появляется база данных версии 3, содержащая хранилище объектов store с индексами name и description .

Дальнейшее чтение

Следующие ресурсы предоставляют дополнительную информацию и контекст для использования IndexedDB.

Документация IndexedDB

Ограничения хранения данных