Trabalhar com IndexedDB

Este guia aborda os conceitos básicos da API IndexedDB. Estamos usando o projeto IndexedDB promised (em inglês) semelhante à API IndexedDB, mas que usa promessas, que você pode await para uma sintaxe mais concisa. Isso simplifica a API e, ao mesmo tempo, para manter a estrutura.

O IndexedDB é um sistema de armazenamento NoSQL em grande escala que permite o armazenamento de apenas sobre qualquer coisa no navegador do usuário. Além dos métodos usuais de pesquisa, get e put ações, o IndexedDB também oferece suporte a transações e é adequado para armazenar grandes quantidades de dados estruturados.

Cada banco de dados IndexedDB é exclusivo para um objeto origin (normalmente o domínio ou subdomínio do site), o que significa que ele não pode acessar ou ser acessado por qualquer outra origem. Os limites de armazenamento de dados são geralmente grandes, se existirem, mas navegadores diferentes lidam com os limites e remoção de dados de maneiras diferentes. Consulte a seção Leitura adicional para mais informações.

Termos do IndexedDB

banco de dados
O nível mais alto do IndexedDB. Ele contém os armazenamentos de objetos, que, por sua vez, contêm os dados que você quer persistir. É possível criar vários bancos de dados com os nomes que você escolher.
Armazenamento de objetos
Um bucket individual para armazenar dados, semelhante às tabelas em bancos de dados relacionais. Normalmente, há um armazenamento de objetos para cada tipo (não dados JavaScript). tipo) dos dados armazenados. Ao contrário das tabelas de banco de dados, os dados JavaScript tipos de dados em um repositório não precisam ser consistentes. Por exemplo, se um app tem um armazenamento de objetos people contendo informações sobre três pessoas, aquelas as propriedades de idade das pessoas poderiam ser 53, 'twenty-five' e unknown.
Índice
Um tipo de armazenamento de objeto para organizar dados em outro armazenamento de objetos (chamado de armazenamento de objetos de referência) por uma propriedade individual dos dados. O índice é usado para recuperar registros no armazenamento de objetos por esta propriedade. Por exemplo, se você estiver armazenar pessoas, convém buscá-las mais tarde por seu nome, idade, ou animal favorito.
Operação
Uma interação com o banco de dados.
Transação
Um wrapper em torno de uma operação ou grupo de operações que garante que o banco de dados integridade dos dados. Se uma das ações em uma transação falhar, nenhuma delas será é aplicada e o banco de dados retorna ao estado em que estava antes da transação começou. Todas as operações de leitura ou gravação no IndexedDB precisam fazer parte de uma transação. Isso permite operações atômicas de leitura-modificação-gravação sem o risco de conflitos com outras linhas de execução atuando no banco de dados ao mesmo tempo.
Cursor
Um mecanismo para iterar vários registros em um banco de dados.

Como verificar o suporte a IndexedDB

O IndexedDB tem suporte universal. Entretanto, se você estiver trabalhando com navegadores mais antigos, não é má ideia suporte à detecção de recursos, por precaução. A maneira mais fácil é conferir o window objeto:

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

Como abrir um banco de dados

Com o IndexedDB, você pode criar vários bancos de dados com qualquer nome que quiser. Se um banco de dados não existe quando você tenta abri-lo, mas não criadas automaticamente. Para abrir um banco de dados, use o método openDB() da biblioteca 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();

Esse método retorna uma promessa que é resolvida em um objeto de banco de dados. Ao usar o botão openDB(), forneça um nome, um número de versão e um objeto de eventos para definir o banco de dados.

Confira um exemplo do método openDB() no contexto:

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

Coloque a verificação do suporte a IndexedDB na parte superior da função anônima. Isso sai da função se o navegador não oferece suporte a IndexedDB. Se a função puder continuar, ele chamará o método openDB() para abrir um banco de dados chamado 'test-db1'. Neste exemplo, o objeto de eventos opcionais foi deixado de fora para manter as coisas simples, mas você precisa especificá-lo para fazer qualquer trabalho significativo com IndexedDB.

Como trabalhar com armazenamentos de objetos

Um banco de dados do IndexedDB contém um ou mais armazenamentos de objetos, cada um com uma coluna para uma chave e outra para os dados associados a essa chave.

Criar armazenamentos de objetos

Um banco de dados IndexedDB bem estruturado precisa ter um repositório de objetos para cada tipo de dados que precisam ser persistidos. Por exemplo, um site que persiste ao usuário os perfis e as observações podem ter um armazenamento de objetos people contendo person e um armazenamento de objetos notes que contém objetos note.

Para garantir a integridade do banco de dados, você só pode criar ou remover armazenamentos de objetos no objeto de eventos em uma chamada openDB(). O objeto de eventos expõe um objeto upgrade() que permite criar armazenamentos de objetos. Chame o método createObjectStore() dentro do método upgrade() para criar o armazenamento de objetos:

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

Esse método recebe o nome do armazenamento de objetos e uma configuração opcional que permite definir várias propriedades para o armazenamento de objetos.

Confira abaixo um exemplo de como usar 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();

Neste exemplo, um objeto de eventos é transmitido ao método openDB() para criar repositório de objetos e, como antes, o trabalho de criação do armazenamento de objetos está concluído no método upgrade() do objeto de evento. No entanto, como o navegador gera uma se você tentar criar um armazenamento de objetos que já existe, recomendamos encapsulando o método createObjectStore() em uma instrução if que verifica se o armazenamento de objetos existe. No bloco if, chame createObjectStore() para criar um repositório de objetos chamado 'firstOS'.

Como definir chaves primárias

Ao definir armazenamentos de objetos, é possível definir como os dados são identificados exclusivamente em armazenamento usando uma chave primária. É possível definir uma chave primária definindo uma ou usando um gerador de chaves.

Um caminho de chave é uma propriedade que sempre existe e contém um valor exclusivo. Para exemplo, no caso de um armazenamento de objetos people, é possível escolher o e-mail como o caminho da chave:

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

Este exemplo cria um armazenamento de objetos chamado 'people' e atribui o email como a chave primária na opção keyPath.

Você também pode usar um gerador de chaves, como autoIncrement. Gerador de chaves cria um valor exclusivo para cada objeto adicionado ao armazenamento de objetos. Por padrão, Se você não especificar uma chave, o IndexedDB criará uma chave e a armazenará separadamente. dos dados.

O exemplo a seguir cria um armazenamento de objetos chamado 'notes' e define o chave primária seja atribuída automaticamente como um número de incremento automático:

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

O exemplo a seguir é semelhante ao anterior, mas, desta vez, o valor de incremento automático foi atribuído explicitamente a uma propriedade chamada '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();

A escolha do método para definir a chave depende dos seus dados. Se as dados têm uma propriedade que é sempre única, você pode torná-lo o keyPath para impor essa exclusividade. Caso contrário, use um valor de incremento automático.

O código a seguir cria três armazenamentos de objetos que demonstram as várias formas de definir chaves primárias em armazenamentos de objetos:

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

Como definir índices

Índices são um tipo de armazenamento de objetos usado para recuperar dados da referência por uma propriedade especificada. Um índice reside dentro do objeto de referência armazenam e contém os mesmos dados, mas usa a propriedade especificada como seu em vez da chave primária do armazenamento de referência. Os índices devem ser feitos quando você cria seus armazenamentos de objetos e pode ser usado para definir uma restrição única sobre seus dados.

Para criar um índice, chame o método createIndex() em uma instância de repositório de objetos:

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

Este método cria e retorna um objeto de índice. O método createIndex() no a instância do repositório de objetos toma o nome do novo índice como a primeira e o segundo argumento se refere à propriedade nos dados que você deseja índice. O argumento final permite definir duas opções que determinam como o índice opera: unique e multiEntry. Se unique estiver definido como true, o índice não permite valores duplicados para uma única chave. A seguir, multiEntry determina como createIndex() se comporta quando a propriedade indexada é uma matriz. Se ele estiver definido como true, createIndex() adicionará uma entrada no índice para cada matriz . Caso contrário, ele adiciona uma única entrada contendo a matriz.

Veja um exemplo:

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

Neste exemplo, os armazenamentos de objetos 'people' e 'notes' têm índices. Para crie os índices, primeiro atribua o resultado de createObjectStore() (um objeto store) a uma variável para chamar createIndex() nela.

Como trabalhar com dados

Esta seção descreve como criar, ler, atualizar e excluir dados. Esses as operações são todas assíncronas, usando promessas em que a API IndexedDB usa solicitações. Isso simplifica a API. Em vez de detectar eventos acionados por solicitação, é possível chamar .then() no objeto de banco de dados retornado do método openDB() para iniciar interações com o banco de dados, ou await seu criação.

Todas as operações de dados no IndexedDB são realizadas em uma transação. Cada tem o seguinte formato:

  1. Recebe o objeto do banco de dados.
  2. Abrir transação no banco de dados.
  3. Abre o armazenamento do objeto na transação.
  4. Realizar a operação no armazenamento de objetos.

Uma transação pode ser considerada um wrapper seguro em torno de uma operação ou um grupo. das operações. Se uma das ações em uma transação falhar, todas as ações são revertidas. As transações são específicas a um ou mais armazenamentos de objetos, que você define ao abrir a transação. Eles podem ser somente leitura ou somente leitura e escrever. Isso indica se as operações dentro da transação leram dados ou fazer alterações no banco de dados.

Criar dados

Para criar dados, chame o método add() na instância do banco de dados e passar os dados que deseja adicionar. O add() o primeiro argumento do método é o armazenamento de objetos ao qual deseja adicionar os dados e o O segundo argumento é um objeto que contém os campos e dados associados que você deseja adicionar. Este é o exemplo mais simples, em que uma única linha de dados é adicionada:

import {openDB} from 'idb';

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

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

addItemToStore();

Cada chamada add() acontece dentro de uma transação, ou seja, mesmo que a promessa seja resolvida com sucesso, isso não significa necessariamente que a operação funcionou. Para ter certeza depois que a operação de adição for realizada, verifique se toda transação foi concluída usando o método transaction.done(). Esta é uma que é resolvida quando a transação é concluída e é rejeitada se o erros de transação. É preciso verificar todas as "gravações" operações porque essa é a única maneira de saber que as alterações no banco de dados acontecido.

O código a seguir mostra o uso do método add() dentro de uma transação:

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

Depois de abrir o banco de dados (e criar um armazenamento de objetos, se necessário), você precisará para abrir uma transação chamando o método transaction() nela. Esse método usa um argumento para a loja em que você quer fazer transações, bem como o modo. Neste caso, queremos escrever para a loja, então este exemplo especifica 'readwrite'.

A próxima etapa é começar a adicionar itens à loja como parte da transação. No exemplo anterior, estamos lidando com três operações no 'foods' store que retornem uma promessa:

  1. Adicionando um registro de um sanduíche saboroso.
  2. Adicionando um registro para alguns ovos.
  3. Sinalizando que a transação foi concluída (tx.done).

Como todas essas ações são baseadas em promessas, precisamos esperar que eles terminarem. Passar essas promessas para Promise.all é uma maneira boa e ergonômica de fazer isso. Promise.all aceita uma matriz de promessas e termina quando todas as promessas passadas a ele são resolvidas.

Para os dois registros que estão sendo adicionados, a interface store da instância da transação chama add() e transmite os dados para ele. Você pode await a chamada Promise.all para que ela seja finalizada quando a transação for concluída.

Ler dados

Para ler os dados, chame o método get() na instância do banco de dados que você recupera usando o método openDB(). get() usa o nome do repositório e o valor da chave primária do objeto que você recuperar. Este é um exemplo básico:

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

Assim como acontece com add(), o método get() retorna uma promessa. Portanto, você pode await se de sua preferência ou use o callback .then() da promessa.

O exemplo a seguir usa o método get() no banco de dados 'test-db4' Armazenamento do objeto 'foods' para receber uma única linha pela chave primária '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();

Recuperar uma única linha do banco de dados é bem simples: abrir banco de dados e especificar o armazenamento de objetos e o valor da chave primária da linha que você que você quer extrair dados. Como o método get() retorna uma promessa, é possível await.

Atualizar dados

Para atualizar os dados, chame o método put() no armazenamento de objetos. O método put() é semelhante ao método add(). e também pode ser usado no lugar de add() para criar dados. Aqui está um exemplo básico de usar put() para atualizar uma linha em um armazenamento de objetos pelo valor da chave primária:

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

Assim como outros métodos, esse método retorna uma promessa. Você também pode usar put() como parte de uma transação. Confira um exemplo que usa a loja 'foods' anterior. que atualiza o preço do sanduíche e dos ovos:

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

A maneira como os itens são atualizados depende de como você define uma chave. Se você definir um keyPath, cada linha no armazenamento de objetos está associada a uma chave inline. As regras atualiza linhas com base nessa chave, e quando você atualiza linhas neste situação, é preciso especificar essa chave para atualizar o item apropriado no armazenamento de objetos. Também é possível criar uma chave fora de linha configurando uma autoIncrement como a chave primária.

Excluir dados

Para excluir dados, chame o método delete() no armazenamento de objetos:

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

Assim como add() e put(), é possível usar isso como parte de uma transação:

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

A estrutura da interação com o banco de dados é a mesma das outras as operações. Lembre-se de verificar se toda a transação foi concluída até incluindo o método tx.done na matriz que você transmite para Promise.all.

Como extrair todos os dados

Até agora, você só recuperou um objeto do armazenamento de cada vez. Você também pode recuperar todos os dados, ou um subconjunto, de um repositório ou índice de objetos usando o método getAll() ou os cursores.

Método getAll()

A maneira mais simples de extrair todos os dados de um armazenamento de objetos é chamar getAll() no repositório ou índice do objeto, da seguinte forma:

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

Esse método retorna todos os objetos no armazenamento de objetos, sem restrições seja qual for o caso. É a maneira mais direta de obter todos os valores de um armazenamento de objetos, mas também a menos flexível.

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

Este exemplo chama getAll() no armazenamento de objetos 'foods'. Isso retorna todos os objetos de 'foods', ordenados pela chave primária.

Como usar cursores

Os cursores são uma maneira mais flexível de recuperar vários objetos. Um cursor seleciona um a um, permitindo que você faça algo com os dados quando são selecionados. Cursores, assim como as outras operações de banco de dados, a trabalhar em transações.

Para criar um cursor, chame openCursor(). no armazenamento de objetos como parte de uma transação. Usando a loja 'foods' de dos exemplos anteriores, isso é como avançar um cursor por todas as linhas de dados na um repositório de objetos:

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

Nesse caso, a transação é aberta no modo 'readonly', e o método openCursor seja chamado. Em uma repetição while subsequente, a linha no a posição atual do cursor pode ter as propriedades key e value lidas, e é possível operar nesses valores da maneira que fizer mais sentido para seu app. Quando estiver tudo pronto, chame o método continue() do objeto cursor. para ir para a próxima linha, e a repetição while termina quando o cursor chega ao final do conjunto de dados.

Usar cursores com intervalos e índices

Os índices permitem buscar os dados em um armazenamento de objetos por uma propriedade diferente da chave primária. É possível criar um índice em qualquer propriedade, que se torna o keyPath para o índice, especifique um intervalo nessa propriedade e obtenha os dados dentro da usando getAll() ou um cursor.

Defina o intervalo usando o objeto IDBKeyRange. e qualquer um dos seguintes métodos:

Os métodos upperBound() e lowerBound() especificam os limites máximos e mínimos do intervalo.

IDBKeyRange.lowerBound(indexKey);

ou:

IDBKeyRange.upperBound(indexKey);

Cada uma usa um argumento: o valor keyPath do índice para o item que você quer. que será especificado como o limite máximo ou mínimo.

O método bound() especifica os limites máximo e mínimo:

IDBKeyRange.bound(lowerIndexKey, upperIndexKey);

O intervalo dessas funções é inclusivo por padrão, ou seja, inclui os dados que é especificado como os limites do intervalo. Para deixar de fora esses valores, especifique o intervalo como exclusivo, passando true como o segundo argumento para lowerBound() ou upperBound(), ou como o terceiro e quarto argumentos de bound() para os limites mínimo e máximo, respectivamente.

O próximo exemplo usa um índice na propriedade 'price' no objeto 'foods' loja on-line. A loja agora também tem um formulário anexado a ela com duas entradas para o limites máximos e mínimos do intervalo. Use o código a seguir para encontrar alimentos com os preços entre esses limites:

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

O código de exemplo primeiro recebe os valores dos limites e verifica se eles existem. O próximo bloco de código decide qual método usar para limitar o intervalo com base nos valores. Na interação com o banco de dados, abra o armazenamento do objeto na transação como de costume, depois abra o índice 'price' no armazenamento de objetos. A Com o índice 'price', você pode pesquisar itens por preço.

Em seguida, o código abre um cursor no índice e passa o intervalo. O cursor retorna uma promessa que representa o primeiro objeto no intervalo, ou undefined se não há dados dentro do intervalo. O método cursor.continue() retorna uma cursor que representa o próximo objeto e continua pelo loop até que você chegar ao fim do intervalo.

Controle de versão do banco de dados

Ao chamar o método openDB(), especifique o número da versão do banco de dados no segundo parâmetro. Em todos os exemplos neste guia, a versão foi definida como 1, mas é possível fazer upgrade de um banco de dados para uma nova versão caso seja necessário modificá-lo de alguma forma. Se a versão especificada for superior à versão do o banco de dados atual, o callback upgrade no objeto de evento é executado, o que permite adicionar novos armazenamentos de objetos e índices ao banco de dados.

O objeto db no callback upgrade tem uma propriedade oldVersion especial, que indica o número da versão do banco de dados ao qual o navegador tem acesso. Você pode transmitir esse número de versão para uma instrução switch para executar blocos de dentro do callback upgrade com base na versão do banco de dados número Veja um exemplo:

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

Neste exemplo, a versão mais recente do banco de dados é definida como 2. Quando esse código é executado pela primeira vez, o banco de dados ainda não existe no navegador, então oldVersion é 0, e a instrução switch começa em case 0. No exemplo, adiciona um armazenamento de objetos 'store' ao banco de dados.

Ponto-chave: em instruções switch, geralmente há um break após cada case. mas isso não é usado deliberadamente aqui. Dessa forma, se o processo o banco de dados está com algumas versões atrasadas ou, se ele não existir, o código continuará pelo restante dos blocos case até que ele esteja atualizado. Então, no exemplo, o navegador continua sendo executado por case 1, criando um índice name no Armazenamento do objeto store.

Para criar um índice 'description' no armazenamento de objetos 'store', atualize o número da versão e adicione um novo bloco case da seguinte maneira:

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

Se o banco de dados que você criou no exemplo anterior ainda existir no navegador, quando for executado, oldVersion será 2. O navegador pula case 0 e case 1 e executa o código em case 2, que cria um description índice. Depois disso, o navegador tem um banco de dados na versão 3, contendo uma store armazenamento de objetos com índices name e description.

Leitura adicional

Os recursos a seguir fornecem mais informações e contexto para o uso do IndexedDB.

Documentação do IndexedDB

Limites de armazenamento de dados