En esta guía, se explican los conceptos básicos de la
API de IndexedDB.
Usaremos la biblioteca
IndexedDB Promised
de Jake Archibald, que es muy similar a la API de IndexedDB, pero usa promesas, que
puedes await para obtener una sintaxis más concisa. Esto simplifica la API y, al mismo tiempo,
mantiene su estructura.
¿Qué es IndexedDB?
IndexedDB es un sistema de almacenamiento NoSQL a gran escala que permite almacenar casi cualquier cosa en el navegador del usuario. Además de las acciones habituales de búsqueda, obtención y colocación, IndexedDB también admite transacciones y es ideal para almacenar grandes cantidades de datos estructurados.
Cada base de datos de IndexedDB es única para un origen (por lo general, el dominio o subdominio del sitio), lo que significa que no puede acceder a ningún otro origen ni ser accedida por él. Sus límites de almacenamiento de datos suelen ser grandes, si es que existen, pero los diferentes navegadores manejan los límites y la expulsión de datos de manera diferente. Consulta la sección Lecturas adicionales para obtener más información.
Términos de IndexedDB
- Base de datos
- Es el nivel más alto de IndexedDB. Contiene los almacenes de objetos, que, a su vez, contienen los datos que deseas conservar. Puedes crear varias bases de datos con los nombres que elijas.
- Almacén de objetos
- Un bucket individual para almacenar datos, similar a las tablas en las bases de datos relacionales.
Por lo general, hay un almacén de objetos para cada tipo (no el tipo de datos de JavaScript) de datos que almacenas. A diferencia de lo que ocurre en las tablas de bases de datos, los tipos de datos
de JavaScript en un almacén no necesitan ser coherentes. Por ejemplo, si una app
tiene un almacén de objetos
peopleque contiene información sobre tres personas, las propiedades de edad de esas personas podrían ser53,'twenty-five'yunknown. - Índice
- Es un tipo de almacén de objetos para organizar los datos en otro almacén de objetos (llamado almacén de objetos de referencia) según una propiedad individual de los datos. El índice se usa para recuperar registros en el almacén de objetos según esta propiedad. Por ejemplo, si almacenas personas, es posible que quieras recuperarlas más adelante por su nombre, edad o animal favorito.
- Operación
- Una interacción con la base de datos
- Transacción
- Es un wrapper alrededor de una operación o un grupo de operaciones que garantiza la integridad de la base de datos. Si una de las acciones de una transacción falla, no se aplica ninguna de ellas y la base de datos vuelve al estado en el que estaba antes de que comenzara la transacción. Todas las operaciones de lectura o escritura en IndexedDB deben formar parte de una transacción. Esto permite operaciones atómicas de lectura, modificación y escritura sin el riesgo de conflictos con otros subprocesos que actúan en la base de datos al mismo tiempo.
- Cursor
- Es un mecanismo para iterar en varios registros de una base de datos.
Cómo verificar la compatibilidad con IndexedDB
IndexedDB tiene una compatibilidad casi universal.
Sin embargo, si trabajas con navegadores más antiguos, no es una mala idea detectar la compatibilidad con las funciones por si acaso. La forma más sencilla es verificar el 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();
Cómo abrir una base de datos
Con IndexedDB, puedes crear varias bases de datos con los nombres que elijas. Si
no existe una base de datos cuando intentas abrirla, se crea automáticamente.
Para abrir una base de datos, usa el método openDB() de la 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();
Este método devuelve una promesa que se resuelve en un objeto de base de datos. Cuando uses el método
openDB(), proporciona un nombre, un número de versión y un objeto de eventos para configurar
la base de datos.
A continuación, se muestra un ejemplo del método openDB() en 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();
Coloca la verificación de compatibilidad con IndexedDB en la parte superior de la función anónima. Esto
sale de la función si el navegador no admite IndexedDB. Si la función puede
continuar, llama al método openDB() para abrir una base de datos llamada 'test-db1'.
En este ejemplo, se omitió el objeto de eventos opcional para simplificar las cosas, pero debes especificarlo para realizar cualquier trabajo significativo con IndexedDB.
Cómo trabajar con almacenes de objetos
Una base de datos de IndexedDB contiene uno o más almacenes de objetos, cada uno de los cuales tiene una columna para una clave y otra para los datos asociados con esa clave.
Crea almacenes de objetos
Una base de datos de IndexedDB bien estructurada debe tener un almacén de objetos para cada tipo
de datos que se deba conservar. Por ejemplo, un sitio que conserva perfiles de usuario y notas puede tener un almacén de objetos people que contenga objetos person y un almacén de objetos notes que contenga objetos note.
Para garantizar la integridad de la base de datos, solo puedes crear o quitar almacenes de objetos en el
objeto de eventos en una llamada openDB(). El objeto de eventos expone un método upgrade()
que te permite crear almacenes de objetos. Llama al método
createObjectStore()
dentro del método upgrade() para crear el almacén 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();
Este método toma el nombre del almacén de objetos y un objeto de configuración opcional que te permite definir varias propiedades para el almacén de objetos.
A continuación, se muestra un ejemplo de cómo 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();
En este ejemplo, se pasa un objeto de eventos al método openDB() para crear
el almacén de objetos y, como antes, el trabajo de crear el almacén de objetos se realiza
en el método upgrade() del objeto de evento. Sin embargo, como el navegador arroja un
error si intentas crear un almacén de objetos que ya existe, te recomendamos
unir el método createObjectStore() en una sentencia if que verifique
si el almacén de objetos existe. Dentro del bloque if, llama a createObjectStore() para crear un almacén de objetos llamado 'firstOS'.
Cómo definir claves primarias
Cuando defines almacenes de objetos, puedes definir cómo se identifican los datos de forma única en el almacén con una clave primaria. Puedes definir una clave primaria con una ruta de acceso a la clave o con un generador de claves.
Una ruta de clave es una propiedad que siempre existe y contiene un valor único. Por ejemplo, en el caso de un almacén de objetos people, puedes elegir la dirección de correo electrónico como la ruta de acceso de la clave:
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();
En este ejemplo, se crea un almacén de objetos llamado 'people' y se asigna la propiedad email
como la clave primaria en la opción keyPath.
También puedes usar un generador de claves, como autoIncrement. El generador de claves
crea un valor único para cada objeto que se agrega al almacén de objetos. De forma predeterminada,
si no especificas una clave, IndexedDB crea una y la almacena por separado
de los datos.
En el siguiente ejemplo, se crea un almacén de objetos llamado 'notes' y se establece que la clave primaria se asigne automáticamente como un número que se incrementa de forma automática:
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();
El siguiente ejemplo es similar al anterior, pero esta vez el valor de incremento automático se asigna de forma explícita a una propiedad llamada '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();
La elección del método que usarás para definir la clave depende de tus datos. Si tus
datos tienen una propiedad que siempre es única, puedes convertirla en la keyPath para
aplicar esta exclusividad. De lo contrario, usa un valor de incremento automático.
En el siguiente código, se crean tres almacenes de objetos que muestran las distintas formas de definir claves primarias en los almacenes 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();
Cómo definir índices
Los índices son un tipo de almacén de objetos que se usa para recuperar datos del almacén de objetos de referencia según una propiedad especificada. Un índice se encuentra dentro del almacén de objetos de referencia y contiene los mismos datos, pero usa la propiedad especificada como su ruta de acceso de clave en lugar de la clave primaria del almacén de referencia. Los índices se deben crear cuando creas tus almacenes de objetos y se pueden usar para definir una restricción única en tus datos.
Para crear un índice, llama al método createIndex()
en una instancia de almacén 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 crea y devuelve un objeto de índice. El método createIndex() en
la instancia del almacén de objetos toma el nombre del índice nuevo como el primer
argumento, y el segundo argumento hace referencia a la propiedad de los datos que deseas
indexar. El argumento final te permite definir dos opciones que determinan cómo
opera el índice: unique y multiEntry. Si unique se establece en true, el
índice no permite valores duplicados para una sola clave. A continuación, multiEntry
determina cómo se comporta createIndex() cuando la propiedad indexada es un array. Si
se establece en true, createIndex() agrega una entrada en el índice para cada elemento
del array. De lo contrario, agrega una sola entrada que contiene el array.
Por ejemplo:
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();
En este ejemplo, los almacenes de objetos 'people' y 'notes' tienen índices. Para
crear los índices, primero asigna el resultado de createObjectStore() (un objeto
de almacén de objetos) a una variable para que puedas llamar a createIndex() en él.
Cómo trabajar con datos
En esta sección, se describe cómo crear, leer, actualizar y borrar datos. Todas estas operaciones son asíncronas y usan promesas en las que la API de IndexedDB usa solicitudes. Esto simplifica la API. En lugar de escuchar los eventos que activa
la solicitud, puedes llamar a .then() en el objeto de base de datos que devuelve el
método openDB() para iniciar interacciones con la base de datos o await su
creación.
Todas las operaciones de datos en IndexedDB se llevan a cabo dentro de una transacción. Cada operación tiene el siguiente formato:
- Obtener el objeto de la base de datos
- Abre una transacción en la base de datos.
- Abre el almacén de objetos en la transacción.
- Realizar una operación en el almacén de objetos.
Una transacción se puede considerar como un wrapper seguro alrededor de una operación o un grupo de operaciones. Si una de las acciones de una transacción falla, se revierten todas las acciones. Las transacciones son específicas de uno o más almacenes de objetos, que defines cuando abres la transacción. Pueden ser de solo lectura o de lectura y escritura. Esto indica si las operaciones dentro de la transacción leen los datos o realizan un cambio en la base de datos.
Crea datos
Para crear datos, llama al método add()
en la instancia de la base de datos y pasa los datos que deseas agregar. El primer argumento del método add() es el almacén de objetos al que deseas agregar los datos, y el segundo argumento es un objeto que contiene los campos y los datos asociados que deseas agregar. Este es el ejemplo más sencillo, en el que se agrega una sola fila de datos:
import {openDB} from 'idb';
async function addItemToStore () {
const db = await openDB('example-database', 1);
await db.add('storeName', {
field: 'data'
});
}
addItemToStore();
Cada llamada a add() se produce dentro de una transacción, por lo que, incluso si la promesa se resuelve
correctamente, no significa necesariamente que la operación haya funcionado. Para asegurarte
de que se haya llevado a cabo la operación de adición, debes verificar si se completó toda
la transacción con el método transaction.done(). Esta es una
promesa que se resuelve cuando se completa la transacción y se rechaza si se producen
errores en la transacción. Debes realizar esta verificación para todas las operaciones de "escritura",
ya que es la única forma de saber si realmente se realizaron los cambios en la base de datos.
En el siguiente código, se muestra el uso del método add() dentro de una transacción:
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();
Una vez que abras la base de datos (y crees un almacén de objetos si es necesario), deberás
abrir una transacción llamando al método transaction() en ella. Este método
toma un argumento para el almacén en el que deseas realizar la transacción, así como el modo.
En este caso, nos interesa escribir en el almacén, por lo que este ejemplo especifica 'readwrite'.
El siguiente paso es comenzar a agregar artículos a la tienda como parte de la transacción.
En el ejemplo anterior, se realizan tres operaciones en el almacén 'foods',
y cada una devuelve una promesa:
- Agregando un registro para un sándwich delicioso.
- Agregando un registro para algunos huevos.
- Indica que se completó la transacción (
tx.done).
Debido a que todas estas acciones se basan en promesas, debemos esperar a que todas
finalicen. Pasar estas promesas a
Promise.all
es una forma agradable y ergonómica de lograrlo. Promise.all acepta un array de promesas y finaliza cuando se resuelven todas las promesas que se le pasaron.
Para los dos registros que se agregan, la interfaz store de la instancia de transacción
llama a add() y le pasa los datos. Puedes await la llamada a Promise.all para que finalice cuando se complete la transacción.
Lee datos
Para leer datos, llama al método get()
en la instancia de la base de datos que recuperas con el método openDB().
get() toma el nombre del almacén y el valor de la clave primaria del objeto que
deseas recuperar. Este es un ejemplo 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();
Al igual que con add(), el método get() devuelve una promesa, por lo que puedes usar await si
lo prefieres o usar la devolución de llamada .then() de la promesa.
En el siguiente ejemplo, se usa el método get() en el almacén de objetos 'foods' de la base de datos 'test-db4' para obtener una sola fila con la clave primaria '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 una sola fila de la base de datos es bastante sencillo: abre
la base de datos y especifica el almacén de objetos y el valor de la clave primaria de la fila de la que
deseas obtener datos. Como el método get() devuelve una promesa, puedes
await.
Cómo actualizar datos
Para actualizar los datos, llama al método put()
en el almacén de objetos. El método put() es similar al método add()
y también se puede usar en lugar de add() para crear datos. Este es un ejemplo básico del uso de put() para actualizar una fila en un almacén de objetos según su valor de clave primaria:
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();
Al igual que otros métodos, este devuelve una promesa. También puedes usar put() como
parte de una transacción. Este es un ejemplo en el que se usa el almacén 'foods' anterior para actualizar el precio del sándwich y los huevos:
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();
La forma en que se actualizan los elementos depende de cómo configures una clave. Si estableces un keyPath,
cada fila del almacén de objetos se asocia con una clave intercalada. En el ejemplo anterior,
se actualizan las filas según esta clave y, cuando actualizas las filas en esta
situación, debes especificar esa clave para actualizar el elemento correspondiente en el
almacén de objetos. También puedes crear una clave fuera de línea si configuras un
autoIncrement como la clave primaria.
Borra datos
Para borrar datos, llama al método delete()
en el almacén 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();
Al igual que add() y put(), puedes usar esto como parte de una transacción:
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();
La estructura de la interacción con la base de datos es la misma que para las otras
operaciones. Recuerda verificar que se haya completado toda la transacción incluyendo el método tx.done en el array que pasas a Promise.all.
Obtén todos los datos
Hasta ahora, solo recuperaste objetos del almacén de a uno por vez. También puedes
recuperar todos los datos, o un subconjunto, de un almacén de objetos o un índice con
el método getAll() o los cursores.
El método getAll()
La forma más sencilla de recuperar todos los datos de un almacén de objetos es llamar a getAll()
en el almacén de objetos o el índice, de la siguiente manera:
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();
Este método devuelve todos los objetos del almacén de objetos, sin ninguna restricción. Es la forma más directa de obtener todos los valores de un almacén de objetos, pero también la menos flexible.
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();
En este ejemplo, se llama a getAll() en el almacén de objetos 'foods'. Esto devuelve todos los objetos de 'foods', ordenados por la clave primaria.
Cómo usar cursores
Los cursores son una forma más flexible de recuperar varios objetos. Un cursor selecciona cada objeto de un almacén de objetos o índice uno por uno, lo que te permite realizar alguna acción con los datos cuando se seleccionan. Los cursores, al igual que las otras operaciones de base de datos, funcionan en transacciones.
Para crear un cursor, llama a openCursor()
en el almacén de objetos como parte de una transacción. Con el almacén 'foods' de los ejemplos anteriores, así es como se avanza un cursor por todas las filas de datos de un almacén 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();
En este caso, la transacción se abre en modo 'readonly' y se llama a su método openCursor. En un bucle while posterior, se pueden leer las propiedades key y value de la fila en la posición actual del cursor, y puedes operar con esos valores de la manera que tenga más sentido para tu app. Cuando todo esté listo, puedes llamar al método continue() del objeto cursor para ir a la siguiente fila, y el bucle while finalizará cuando el cursor llegue al final del conjunto de datos.
Usa cursores con rangos e índices
Los índices te permiten recuperar los datos de un almacén de objetos por una propiedad que no sea la clave primaria. Puedes crear un índice en cualquier propiedad, que se convierte en la keyPath
del índice, especificar un rango en esa propiedad y obtener los datos dentro del
rango con getAll() o un cursor.
Define tu rango con el objeto IDBKeyRange y cualquiera de los siguientes métodos:
upperBound().lowerBound().bound()(que es ambas cosas).only().includes().
Los métodos upperBound() y lowerBound() especifican los límites superior e inferior del rango.
IDBKeyRange.lowerBound(indexKey);
o:
IDBKeyRange.upperBound(indexKey);
Cada uno toma un argumento: el valor keyPath del índice para el elemento que deseas
especificar como límite superior o inferior.
El método bound() especifica un límite superior y uno inferior:
IDBKeyRange.bound(lowerIndexKey, upperIndexKey);
El rango de estas funciones es inclusivo de forma predeterminada, lo que significa que incluye
los datos que se especifican como los límites del rango. Para omitir esos valores,
especifica el rango como exclusivo pasando true como el segundo argumento para
lowerBound() o upperBound(), o como el tercer y cuarto argumento de
bound(), para los límites inferior y superior, respectivamente.
En el siguiente ejemplo, se usa un índice en la propiedad 'price' del almacén de objetos
'foods'. La tienda ahora también tiene un formulario adjunto con dos entradas para los límites superior e inferior del rango. Usa el siguiente código para encontrar alimentos con precios entre esos límites:
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);
En el código de ejemplo, primero se obtienen los valores de los límites y se verifica si estos
existen. En el siguiente bloque de código, se decide qué método usar para limitar el rango
en función de los valores. En la interacción con la base de datos, abre el almacén de objetos en la transacción como de costumbre y, luego, abre el índice 'price' en el almacén de objetos. El índice 'price' te permite buscar artículos por precio.
Luego, el código abre un cursor en el índice y pasa el rango. El cursor
devuelve una promesa que representa el primer objeto del rango o undefined si
no hay datos dentro del rango. El método cursor.continue() devuelve un
cursor que representa el siguiente objeto y continúa con el bucle hasta que
llegues al final del rango.
Control de versiones de la base de datos
Cuando llamas al método openDB(), puedes especificar el número de versión de la base de datos
en el segundo parámetro. En todos los ejemplos de esta guía, la versión se estableció en 1, pero una base de datos se puede actualizar a una versión nueva si necesitas modificarla de alguna manera. Si la versión especificada es mayor que la versión de
la base de datos existente, se ejecuta la devolución de llamada upgrade en el objeto de evento,
lo que te permite agregar nuevos almacenes de objetos e índices a la base de datos.
El objeto db en la devolución de llamada upgrade tiene una propiedad oldVersion especial,
que indica el número de versión de la base de datos a la que tiene acceso el navegador.
Puedes pasar este número de versión a una sentencia switch para ejecutar bloques de
código dentro de la devolución de llamada upgrade según el número de versión de la base de datos
existente. Por ejemplo:
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');
}
}
});
En este ejemplo, se establece la versión más reciente de la base de datos en 2. Cuando este código
se ejecuta por primera vez, la base de datos aún no existe en el navegador, por lo que oldVersion
es 0 y la sentencia switch comienza en case 0. En el ejemplo, esto
agrega un almacén de objetos 'store' a la base de datos.
Punto clave: En las sentencias switch, suele haber un break después de cada bloque case, pero aquí no se usa de forma deliberada. De esta manera, si la base de datos existente tiene algunas versiones anteriores o si no existe, el código continúa por el resto de los bloques case hasta que se actualiza. Por lo tanto, en el ejemplo,
el navegador sigue ejecutándose a través de case 1 y crea un índice name en el
almacén de objetos store.
Para crear un índice 'description' en el almacén de objetos 'store', actualiza el
número de versión y agrega un nuevo bloque case de la siguiente manera:
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');
}
}
});
Si la base de datos que creaste en el ejemplo anterior aún existe en el navegador,
cuando se ejecute, oldVersion será 2. El navegador omite case 0 y
case 1, y ejecuta el código en case 2, que crea un índice
description. Después de eso, el navegador tiene una base de datos en la versión 3 que contiene un almacén de objetos store con índices name y description.
Lecturas adicionales
Los siguientes recursos proporcionan más información y contexto para usar IndexedDB.
Documentación de IndexedDB
- Repositorio de GitHub de
idb - Cómo usar IndexedDB
- Conceptos básicos de IndexedDB
- Especificación de la API de Indexed Database 3.0