Neste guia, abordamos 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 usadas como await
para uma sintaxe mais concisa. Isso simplifica a API e mantém a estrutura dela.
O que é o IndexedDB?
O IndexedDB é um sistema de armazenamento NoSQL em grande escala que permite armazenar praticamente qualquer item no navegador do usuário. Além das ações comuns de pesquisa, busca e put, o IndexedDB também é compatível com transações e é adequado para armazenar grandes quantidades de dados estruturados.
Cada banco de dados IndexedDB é exclusivo para uma 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 geralmente são grandes, se existirem, mas diferentes navegadores processam limites e remoções 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 de IndexedDB. Ela contém os armazenamentos de objetos, que, por sua vez, contêm os dados que você quer manter. É possível criar vários bancos de dados com quaisquer nomes que você escolher.
- Repositório 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 JavaScript) de dados que você armazena. Ao contrário das tabelas de banco de dados, os tipos de dados JavaScript
em um armazenamento não precisam ser consistentes. Por exemplo, se um app
tiver um repositório de objetos
people
que contém informações sobre três pessoas, as propriedades de idade delas poderão ser53
,'twenty-five'
eunknown
. - Índice
- Um tipo de armazenamento de objetos 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 por essa propriedade para recuperar registros no armazenamento de objetos. Por exemplo, se você estiver armazenando pessoas, poderá buscar mais tarde pelo nome, idade ou animais 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-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 a compatibilidade com IndexedDB
O IndexedDB tem suporte universal (link em inglês).
No entanto, se você estiver trabalhando com navegadores mais antigos, não é uma má 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, você pode criar vários bancos de dados com o nome que quiser. 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, número de versão e um objeto de eventos para configurar
o banco de dados.
Confira um exemplo do método openDB()
em 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();
Faça a verificação do suporte ao IndexedDB na parte superior da função anônima. A função
será encerrada se o navegador não for compatível com o IndexedDB. Se a função puder
continuar, ela 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 simplificar, mas você precisa especificá-lo para fazer qualquer trabalho significativo com o IndexedDB.
Como trabalhar com armazenamentos de objetos
Um banco de dados 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 repositórios de objetos
Um banco de dados IndexedDB bem estruturado precisa ter um armazenamento de objetos para cada tipo
de dados que precisam ser mantidos. Por exemplo, um site que mantém perfis
e notas de usuário pode ter um repositório de objetos people
, que contém objetos person
,
e um repositório de objeto notes
, contendo objetos note
.
Para garantir a integridade do banco de dados, só é possível criar ou remover armazenamentos de objetos no
objeto de eventos em uma chamada openDB()
. O objeto de eventos expõe um método 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 usa o nome do armazenamento de objetos e um objeto de configuração opcional que permite definir várias propriedades para o repositório.
Confira a seguir um exemplo de como usar o 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 armazenamento de objetos e, como antes, o trabalho de criação do armazenamento de objetos é feito no método upgrade()
do objeto de evento. No entanto, como o navegador gera um
erro se você tentar criar um armazenamento de objetos que já existe, recomendamos
unir o método createObjectStore()
em uma instrução if
que verifica
se esse armazenamento existe. No bloco if
, chame
createObjectStore()
para criar um armazenamento de objetos chamado 'firstOS'
.
Como definir chaves primárias
Ao definir armazenamentos de objetos, é possível definir como os dados são identificados exclusivamente nele usando uma chave primária. Você pode definir uma chave primária ao definir um caminho de chave ou usar 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 armazenamento de objeto people
, é possível 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 armazenamento de objetos chamado 'people'
e atribui a propriedade email
como chave primária na opção keyPath
.
Também é possível usar um gerador de chaves, como o autoIncrement
. O gerador de chaves
cria um valor exclusivo para cada objeto adicionado ao armazenamento. 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 a
chave primária a 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 têm uma propriedade que é sempre exclusiva, é possível torná-los 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, demonstrando as várias maneiras 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 do armazenamento 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 caminho de chave em vez da chave primária do armazenamento de referência. Os índices precisam ser criados quando você cria seus armazenamentos 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 armazenamento 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 funciona: unique
e multiEntry
. Se unique
for definido como true
, o
índice não permitirá valores duplicados para uma única chave. Em seguida, multiEntry
determina como createIndex()
se comporta quando a propriedade indexada é uma matriz. Se
estiver definido como true
, createIndex()
vai adicionar uma entrada no índice para cada elemento
da 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 criar os índices, primeiro atribua o resultado de createObjectStore()
(um objeto de armazenamento de objetos) a uma variável para que você possa chamar createIndex()
nele.
Como trabalhar com dados
Esta seção descreve como criar, ler, atualizar e excluir dados. Essas
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 pela
solicitação, você pode chamar .then()
no objeto do 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:
- Recebe o objeto do banco de dados.
- Abrir transação no banco de dados.
- Abrir armazenamento de objetos na transação.
- Executar operação no armazenamento de objetos.
Uma transação pode ser considerada um wrapper seguro em torno de uma operação ou de um 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 a um ou mais armazenamentos de objetos, que você define quando abre a transação. Eles podem ser somente leitura ou de leitura e gravação. Isso significa 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 armazenamento de objetos a que você quer adicionar os dados, e o segundo argumento é um objeto que contém os campos e os dados associados que você quer 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. Portanto, mesmo que a promessa seja resolvida
com sucesso, isso não significa necessariamente que a operação funcionou. Para garantir
que a operação de adição tenha sido realizada, é necessário verificar 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 em caso de erros. Você precisa realizar essa verificação para todas as operações de "gravação",
porque essa é sua única maneira de saber que as alterações no banco de dados realmente
ocorreram.
O código a seguir mostra o uso do método add()
em 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á
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, estamos interessados em gravar na 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 armazenamento 'foods'
,
cada uma delas retorna uma promessa:
- Adicionando um registro de um sanduíche saboroso.
- Adicionando um registro para alguns ovos.
- Indicaçã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 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 de transação chama add()
e transmite os dados para ele. É possível usar await
na chamada Promise.all
para que ela seja concluída quando a transação for concluída.
Ler dados
Para ler dados, chame o método get()
na instância do banco de dados recuperada usando o método openDB()
.
get()
usa o nome do armazenamento e o valor da chave primária do objeto que você
quer recuperar. Aqui está 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, para que você possa await
, se preferir, ou usar o callback .then()
da promessa.
O exemplo a seguir usa o método get()
no armazenamento 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 é bem simples: abra
o banco de dados e especifique o armazenamento de objetos e o valor da chave primária da linha da qual
você quer receber dados. Como o método get()
retorna uma promessa, você pode usar await
.
Atualizar dados
Para atualizar dados, chame o método put()
no armazenamento de objetos. O método put()
é semelhante ao add()
e também pode ser usado no lugar de add()
para criar dados. Confira um exemplo básico
de como 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();
Como outros métodos, esse método retorna uma promessa. Também é possível usar put()
como
parte de uma transação. Veja um exemplo usando 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 será associada a uma chave inline. O exemplo anterior atualiza linhas com base nessa chave e, quando você atualiza linhas nessa situação, precisa especificar essa chave para atualizar o item apropriado no armazenamento 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 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 do banco de dados é a mesma das outras
operações. Lembre-se de verificar se toda a transação foi concluída
incluindo o método tx.done
na matriz transmitida para Promise.all
.
Como obter todos os dados
Até agora, você só recuperou um objeto da loja por vez. Também é possível
recuperar todos os dados, ou um subconjunto, de um repositório ou índice de objetos usando
o método getAll()
ou cursores.
Método getAll()
A maneira mais simples de recuperar todos os dados de um armazenamento de objetos é chamar getAll()
no armazenamento 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 armazenamento de objetos, sem qualquer restrição. É a maneira mais direta de conseguir 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 cada objeto em um armazenamento de objetos ou indexa um por um, permitindo que você faça algo com os dados quando eles estiverem selecionados. Cursores, assim como as outras operações de banco de dados, funcionam em transações.
Para criar um cursor, chame openCursor()
no armazenamento de objetos como parte de uma transação. Veja como avançar um cursor por todas as linhas de dados em um armazenamento de objetos usando o armazenamento 'foods'
dos exemplos anteriores:
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
é chamado. Em uma repetição while
subsequente, a linha na posição atual do cursor pode ter suas propriedades key
e value
lidas, e você pode operar nesses valores da maneira que fizer mais sentido para seu aplicativo. Quando estiver pronto, chame o método continue()
do objeto cursor
para ir para a próxima linha, e o loop while
será encerrado quando o cursor chegar ao fim 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
do índice, especificar um intervalo nessa propriedade e acessar 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()
(ambos).only()
.includes()
.
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 delas usa um argumento: o valor keyPath
do índice para o item que você quer
especificar como o limite máximo ou mínimo.
O método bound()
especifica um limite máximo e mínimo:
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 de fora esses valores,
especifique o intervalo como exclusivo transmitindo true
como o segundo argumento para
lowerBound()
ou upperBound()
, ou como o terceiro e quarto argumentos de
bound()
, para os limites inferior e superior, respectivamente.
O próximo exemplo usa um índice na propriedade 'price'
no armazenamento de objetos 'foods'
. O repositório agora também tem um formulário anexado com duas entradas para os
limites máximo e mínimo do intervalo. Use o código a seguir 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);
Primeiro, o código de exemplo acessa 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 do banco de dados, abra o armazenamento de objetos na
transação como de costume e, em seguida, abra o índice 'price'
no armazenamento de objetos. O
índice 'price'
permite pesquisar itens por preço.
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 houver dados no intervalo. O método cursor.continue()
retorna um cursor que representa o próximo objeto e continua pelo loop até chegar ao fim do intervalo.
Controle de versões 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
modificar algo. Se a versão especificada for mais recente que a versão 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 a que o navegador tem acesso.
É possível transmitir esse número de versão em 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. 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, então oldVersion
é 0
, e a instrução switch
começa em case 0
. No exemplo, isso
adiciona um armazenamento de objetos 'store'
ao banco de dados.
Importante: nas instruções switch
, geralmente há um break
após cada bloco case
, mas isso não é usado aqui deliberadamente. Dessa forma, se o banco de dados
já existente estiver algumas versões atrás de outras ou se ele não existir, o código continuará
através do restante dos blocos case
até ficar atualizado. Portanto,
no exemplo, o navegador continua a execução com case 1
, criando um índice name
no
armazenamento de objetos 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 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 armazenamento de objetos
store
com índices name
e description
.
Leia mais
Os recursos a seguir fornecem mais informações e contexto para usar o IndexedDB.
Documentação do IndexedDB
- Repositório
idb
do GitHub (em inglês) - Como usar o IndexedDB
- Conceitos básicos do IndexedDB
- Especificação da API Indexed Database 3.0