Este guia aborda os conceitos básicos da
API IndexedDB.
Estamos usando a biblioteca
IndexedDB Promised
de Jake Archibald, que é muito semelhante à API IndexedDB, mas usa promessas, que
podem ser await para uma sintaxe mais concisa. Isso simplifica a API, mas
mantém a estrutura dela.
O que é o IndexedDB?
O IndexedDB é um sistema de armazenamento NoSQL em grande escala que permite armazenar quase tudo no navegador do usuário. Além das ações comuns de pesquisa, recebimento e colocação, o IndexedDB também oferece suporte a transações e é adequado para armazenar grandes quantidades de dados estruturados.
Cada banco de dados do IndexedDB é exclusivo de uma origem (geralmente o domínio ou subdomínio do site), o que significa que ele não pode acessar nem ser acessado por nenhuma outra origem. Os limites de armazenamento de dados geralmente são grandes, se existirem, mas navegadores diferentes lidam com limites e remoção de dados de maneira diferente. 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 repositórios de objetos, que, por sua vez, contêm os dados que você quer manter. É possível criar vários bancos de dados com os nomes que você escolher.
- Repositório de objetos
- Um bucket individual para armazenar dados, semelhante a tabelas em bancos de dados relacionais.
Normalmente, há um repositório de objetos para cada tipo (não o tipo de dados do JavaScript) de dados que você está armazenando. Ao contrário das tabelas de banco de dados, os tipos de dados
JavaScript em um repositório não precisam ser consistentes. Por exemplo, se um app
tiver um repositório de objetos
peoplecom informações sobre três pessoas, as propriedades de idade dessas pessoas poderão ser53,'twenty-five'eunknown. - Índice
- Um tipo de repositório de objetos para organizar dados em outro repositório de objetos (chamado de repositório de objetos de referência) por uma propriedade individual dos dados. O índice é usado para recuperar registros no repositório de objetos por essa propriedade. Por exemplo, se você estiver armazenando pessoas, talvez queira buscá-las mais tarde pelo 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 a integridade do banco de dados. Se uma das ações em uma transação falhar, nenhuma delas será aplicada, e o banco de dados retornará ao estado em que estava antes do início da transação. 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 e gravação sem o risco de conflitos com outras linhas de execução que atuam no banco de dados ao mesmo tempo.
- Cursor
- Um mecanismo para iterar em vários registros em um banco de dados.
Como verificar se há suporte para IndexedDB
O IndexedDB é compatível com quase todos os navegadores.
No entanto, se você estiver trabalhando com navegadores mais antigos, é uma boa ideia
detectar o suporte a recursos, por precaução. A maneira mais fácil é verificar o objeto
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();
Como abrir um banco de dados
Com o IndexedDB, é possível criar vários bancos de dados com os nomes que você escolher. Se
um banco de dados não existir quando você tentar abri-lo, ele será criado 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 método openDB(), forneça um nome, um número de versão e um objeto de eventos para configurar 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 de suporte ao IndexedDB na parte de cima da função anônima. Isso
sai da função se o navegador não for compatível com o IndexedDB. Se a função puder
continuar, ela vai chamar o método openDB() para abrir um banco de dados chamado 'test-db1'.
Neste exemplo, o objeto de eventos opcional foi deixado de fora para simplificar, mas você precisa especificá-lo para fazer qualquer trabalho significativo com o IndexedDB.
Como trabalhar com repositórios de objetos
Um banco de dados IndexedDB contém um ou mais repositórios de objetos, cada um com uma coluna para uma chave e outra para os dados associados a essa chave.
Criar repositórios de objetos
Um banco de dados IndexedDB bem estruturado precisa ter um repositório de objetos para cada tipo
de dados que precisa ser mantido. Por exemplo, um site que mantém perfis de usuário e notas pode ter um repositório de objetos people contendo objetos person e um repositório de objetos notes contendo objetos note.
Para garantir a integridade do banco de dados, só é possível criar ou remover repositórios de objetos no objeto de eventos em uma chamada openDB(). O objeto de eventos expõe um método upgrade()
que permite criar repositórios de objetos. Chame o método
createObjectStore()
dentro do método upgrade() para criar o repositório 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 usa o nome do repositório de objetos e um objeto de configuração opcional que permite definir várias propriedades para o repositório de objetos.
Confira 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
o repositório de objetos. Como antes, o trabalho de criação do repositório de objetos é feito
no método upgrade() do objeto de evento. No entanto, como o navegador gera um
erro se você tentar criar um repositório de objetos que já existe, recomendamos
encapsular o método createObjectStore() em uma instrução if que verifique
se o repositório de objetos existe. No bloco if, chame
createObjectStore() para criar um repositório de objetos chamado 'firstOS'.
Como definir chaves primárias
Ao definir repositórios de objetos, é possível definir como os dados são identificados de forma exclusiva no repositório usando uma chave primária. É possível definir uma chave primária definindo um caminho de chave ou usando um gerador de chaves.
Um caminho de chave é uma propriedade que sempre existe e contém um valor exclusivo. Por exemplo, no caso de um repositório de objetos people, você pode escolher o endereço de 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 repositório de objetos chamado 'people' e atribui a propriedade email
como a chave primária na opção keyPath.
Também é possível usar um gerador de chaves, como autoIncrement. O gerador de chaves
cria um valor exclusivo para cada objeto adicionado ao repositório de objetos. Por padrão,
se você não especificar uma chave, o IndexedDB vai criar uma e armazená-la separadamente
dos dados.
O exemplo a seguir cria um repositório de objetos chamado 'notes' e define a
chave primária para ser 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 é explicitamente atribuído 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 os
dados tiverem uma propriedade que é sempre exclusiva, você poderá torná-la o keyPath para
aplicar essa exclusividade. Caso contrário, use um valor de incremento automático.
O código a seguir cria três repositórios de objetos, demonstrando as várias maneiras de definir chaves primárias neles:
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
Os índices são um tipo de repositório de objetos usado para recuperar dados do repositório de objetos de referência por uma propriedade especificada. Um índice reside no repositório de objetos de referência e contém os mesmos dados, mas usa a propriedade especificada como o caminho da chave em vez da chave primária do repositório de referência. Os índices precisam ser criados quando você cria os repositórios de objetos e podem ser usados para definir uma restrição exclusiva nos 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();
Esse método cria e retorna um objeto de índice. O método createIndex() na
instância do repositório de objetos usa o nome do novo índice como o primeiro
argumento, e o segundo argumento se refere à propriedade nos dados que você quer
indexar. O argumento final permite definir duas opções que determinam como o índice opera: unique e multiEntry. Se unique for definido como true, o
índice não vai permitir valores duplicados para uma única chave. Em seguida, multiEntry
determina como createIndex() se comporta quando a propriedade indexada é uma matriz. Se
definido como true, o createIndex() adiciona uma entrada no índice para cada elemento
da matriz. Caso contrário, ela 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 repositórios de objetos 'people' e 'notes' têm índices. Para
criar os índices, primeiro atribua o resultado de createObjectStore() (um objeto
de repositório de objetos) a uma variável para que você possa chamar createIndex() nele.
Como trabalhar com dados
Nesta seção, descrevemos como criar, ler, atualizar e excluir dados. Todas essas
operações são assíncronas, usando promessas em que a API IndexedDB usa
solicitações. Isso simplifica a API. Em vez de detectar eventos acionados pela
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 a
criação dele.
Todas as operações de dados no IndexedDB são realizadas dentro de uma transação. Cada operação tem o seguinte formato:
- Receber objeto de banco de dados.
- Abra a transação no banco de dados.
- Abra o repositório de objetos na transação.
- Realizar operação no repositório de objetos.
Uma transação pode ser considerada um wrapper seguro em torno de uma operação ou grupo de operações. Se uma das ações em uma transação falhar, todas as ações serão revertidas. As transações são específicas para um ou mais repositórios de objetos, que você define ao abrir a transação. Eles podem ser somente leitura ou leitura e gravação. Isso indica se as operações dentro da transação leem os dados ou fazem uma alteração no banco de dados.
Criar dados
Para criar dados, chame o método add()
na instância do banco de dados e transmita os dados que você quer adicionar. O primeiro argumento do método add() é o repositório de objetos em que você quer adicionar os dados, e o segundo é um objeto que contém os campos e os dados associados que você quer adicionar. Veja 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 em uma transação. Portanto, mesmo que a promessa seja resolvida, isso não significa necessariamente que a operação funcionou. Para garantir que
a operação de adição foi realizada, verifique se toda a
transação foi concluída usando o método transaction.done(). Essa é uma promessa que é resolvida quando a transação é concluída e rejeitada se houver erros na transação. É preciso realizar essa verificação para todas as operações de "gravação",
porque essa é a única maneira de saber se as mudanças no banco de dados realmente
ocorreram.
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 repositório de objetos, se necessário), você precisará
abrir uma transação chamando o método transaction() nele. Esse método
usa um argumento para a loja em que você quer fazer a transação, além do modo.
Nesse caso, queremos gravar no repositório, 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 armazenamento 'foods',
cada uma retornando uma promessa:
- Adicionando um registro para um sanduíche saboroso.
- Adicionando um registro para ovos.
- Sinalização de que a transação foi concluída (
tx.done).
Como todas essas ações são baseadas em promessas, precisamos esperar que todas
elas sejam concluídas. Transmitir essas promessas para
Promise.all
é uma maneira prática e ergonômica de fazer isso. Promise.all aceita uma matriz de
promessas e termina quando todas as promessas transmitidas a ela são resolvidas.
Para os dois registros que estão sendo adicionados, a interface store da instância de transação
chama add() e transmite os dados para ela. É possível await a chamada Promise.all
para que ela seja encerrada quando a transação for concluída.
Ler dados
Para ler 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 armazenamento e o valor da chave primária do objeto que você
quer recuperar. Confira 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 em add(), o método get() retorna uma promessa. Portanto, é possível usar await se
preferir ou o callback .then() da promessa.
O exemplo a seguir usa o método get() no repositório de objetos 'foods' do banco de dados 'test-db4' 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 é bastante simples: abra
o banco de dados e especifique o repositório de objetos e o valor da chave primária da linha de onde
você quer receber os dados. Como o método get() retorna uma promessa, é possível usar await nela.
Atualizar dados
Para atualizar dados, chame o método put()
no repositório de objetos. O método put() é semelhante ao método add()
e também pode ser usado no lugar de add() para criar dados. Veja um exemplo básico
de como usar put() para atualizar uma linha em um repositório 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();
Como outros métodos, esse retorna uma promessa. Também é possível usar put() como
parte de uma transação. Veja um exemplo que usa o armazenamento '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 forma como os itens são atualizados depende de como você define uma chave. Se você definir um keyPath,
cada linha no repositório de objetos será associada a uma chave inline. O exemplo anterior
atualiza as linhas com base nessa chave. Quando você atualiza as linhas nessa
situação, é necessário especificar essa chave para atualizar o item apropriado no
repositório de objetos. Também é possível criar uma chave fora de linha definindo um
autoIncrement como a chave primária.
Excluir dados
Para excluir dados, chame o método delete()
no repositório 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 é igual à das outras
operações. Verifique se toda a transação foi concluída
incluindo o método tx.done na matriz que você transmite para Promise.all.
Como receber todos os dados
Até agora, você só recuperou objetos da loja um por um. Também é possível
recuperar todos os dados, ou um subconjunto, de um repositório de objetos ou índice usando
o método getAll() ou cursores.
O método getAll()
A maneira mais simples de recuperar todos os dados de um repositório de objetos é chamar getAll()
no repositório ou índice de objetos, desta 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 repositório de objetos, sem nenhuma restrição. É a maneira mais direta de receber todos os valores de um repositório 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 repositório 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 cada objeto em um repositório de objetos ou índice, um por um, permitindo que você faça algo com os dados quando eles são selecionados. Os cursores, assim como as outras operações de banco de dados, funcionam em transações.
Para criar um cursor, chame openCursor()
no repositório de objetos como parte de uma transação. Usando o repositório 'foods' dos
exemplos anteriores, veja como avançar um cursor por todas as linhas de dados em
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 dela é chamado. Em um loop while subsequente, a linha na
posição atual do cursor pode ter as propriedades key e value lidas, e
você pode operar esses 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. O loop while é encerrado quando o cursor
chega ao fim do conjunto de dados.
Usar cursores com intervalos e índices
Os índices permitem buscar os dados em um repositório de objetos por uma propriedade diferente da
chave primária. É possível criar um índice em qualquer propriedade, que se torna o keyPath
do índice, especificar um intervalo nessa propriedade e receber os dados dentro do
intervalo usando getAll() ou um cursor.
Defina o intervalo usando o objeto IDBKeyRange e qualquer um dos seguintes
métodos:
upperBound().lowerBound().bound()(que é os dois).only().includes().
Os métodos upperBound() e lowerBound() especificam os limites superior e inferior do intervalo.
IDBKeyRange.lowerBound(indexKey);
ou:
IDBKeyRange.upperBound(indexKey);
Cada um deles usa um argumento: o valor keyPath do índice para o item que você quer
especificar como o limite superior ou inferior.
O método bound() especifica um limite superior e um inferior:
IDBKeyRange.bound(lowerIndexKey, upperIndexKey);
O intervalo dessas funções é inclusivo por padrão, o que significa que ele inclui
os dados especificados como os limites do intervalo. Para deixar esses valores de fora,
especifique o intervalo como exclusivo transmitindo true como o segundo argumento para
lowerBound() ou upperBound(), ou como o terceiro e o quarto argumentos de
bound(), para os limites inferior e superior, respectivamente.
O próximo exemplo usa um índice na propriedade 'price' no repositório de objetos
'foods'. A loja agora também tem um formulário anexado com duas entradas para os
limites superior e inferior do intervalo. Use o código abaixo para encontrar alimentos com
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 repositório de objetos na
transação como de costume e, em seguida, abra o índice 'price' no repositório de objetos. O índice 'price' permite pesquisar itens por preço.
Em seguida, o código abre um cursor no índice e transmite o intervalo. O cursor
retorna uma promessa que representa o primeiro objeto no intervalo ou undefined se
não houver dados no intervalo. O método cursor.continue() retorna um
cursor que representa o próximo objeto e continua pelo loop até que você
chegue ao fim do intervalo.
Controle de versões de banco de dados
Ao chamar o método openDB(), é possível especificar o número da versão do banco de dados
no segundo parâmetro. Em todos os exemplos deste guia, a versão foi
definida como 1, mas um banco de dados pode ser atualizado para uma nova versão se você precisar
modificá-lo de alguma forma. Se a versão especificada for maior que a do banco de dados atual, o callback upgrade no objeto de evento será executado, permitindo que você adicione novos repositórios 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 que o navegador pode acessar.
É possível transmitir esse número de versão para uma instrução switch para executar blocos de
código dentro do callback upgrade com base no número da versão do banco de dados
existente. 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');
}
}
});
Este exemplo define a versão mais recente do banco de dados como 2. Quando esse código
é executado pela primeira vez, o banco de dados ainda não existe no navegador. Portanto, oldVersion
é 0, e a instrução switch começa em case 0. No exemplo, isso
adiciona um repositório de objetos 'store' ao banco de dados.
Ponto-chave: em instruções switch, geralmente há um break após cada bloco
case, mas isso não é usado aqui de forma deliberada. Dessa forma, se o banco de dados
atual estiver algumas versões desatualizado ou se não existir, o código vai continuar
pelos demais blocos case até que esteja atualizado. No exemplo,
o navegador continua a execução por meio de case 1, criando um índice name no
repositório de objetos store.
Para criar um índice 'description' no repositório 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 criado no exemplo anterior ainda existir no navegador,
quando isso for executado, oldVersion será 2. O navegador ignora case 0 e
case 1 e executa o código em case 2, que cria um índice
description. Depois disso, o navegador tem um banco de dados na versão 3 que contém um repositório de objetos
store com os í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
- Repositório do GitHub do
idb - Como usar o IndexedDB
- Conceitos básicos do IndexedDB
- Especificação da API Indexed Database 3.0