Trabaja con IndexedDB

En esta guía, se tratan los aspectos básicos API de IndexedDB. Usamos la serie de Jake Archibald IndexedDB prometido muy similar a la API de IndexedDB, pero usa promesas, que puedes await para una sintaxis más concisa. Esto simplifica la API mientras y mantener su estructura.

¿Qué es IndexedDB?

IndexedDB es un sistema de almacenamiento NoSQL a gran escala que permite almacenar sobre cualquier elemento en el navegador del usuario. Además de los métodos de búsqueda habituales, putt de acciones, IndexedDB también admite transacciones y es adecuado para para almacenar grandes cantidades de datos estructurados.

Cada base de datos IndexedDB es única para un origen. (por lo general, el dominio o subdominio del sitio), lo que significa que no se puede acceder al sitio de cualquier otro origen. Sus límites de almacenamiento de datos suelen ser grandes (en caso de que existan), pero distintos navegadores controlan 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 múltiples bases de datos con los nombres que elijas.
Almacén de objetos
Un bucket individual para almacenar datos, similar a las tablas de las bases de datos relacionales. Por lo general, hay un depósito de objetos para cada tipo (no datos de JavaScript). de los datos que almacenas. A diferencia de las tablas de bases de datos, los datos de JavaScript tipos de datos en una tienda no necesitan ser coherentes. Por ejemplo, si una aplicación tiene un almacén de objetos people que contiene información sobre tres personas, esas las propiedades de la edad de las personas podrían ser 53, 'twenty-five' y unknown.
Índice
Es un tipo de almacén de objetos para organizar datos en otro almacén de objetos (denominado un almacén de objetos de referencia) por una propiedad individual de los datos. El índice se usa para recuperar registros en el almacén de objetos mediante esta propiedad. Por ejemplo, si estás almacenando personas. Es posible que quieras buscarlas más tarde por su nombre, edad o animal favorito.
Operación
Una interacción con la base de datos.
Transacción
Wrapper de una operación o grupo de operaciones que garantiza que la base de datos para mantener la integridad de tus datos. Si una de las acciones en una transacción falla, ninguna de ellas se aplicada y la base de datos vuelve al estado en el que estaba antes de la transacción comenzó. Todas las operaciones de lectura o escritura en IndexedDB deben ser parte de una transacción. Esto permite operaciones atómicas de lectura, modificación y escritura sin riesgo de conflictos. mientras que otros subprocesos 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 es casi compatible con todos los usuarios. Sin embargo, si trabajas con navegadores más antiguos, no es una mala idea para detectar atributos por si acaso. La forma más fácil es verificar el 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();

Cómo abrir una base de datos

Con IndexedDB, puedes crear varias bases de datos con el nombre que elijas. Si una base de datos no existe cuando intentas abrirla, se crean 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 muestra una promesa que se resuelve en un objeto de base de datos. Cuando uses openDB(), proporciona un nombre, un número de versión y un objeto de eventos para establecer para configurar la base de datos.

Este es 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. Esta 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, el objeto de eventos opcional se excluyó para mantener las cosas simple, pero debes especificarlo para que realice un trabajo significativo con IndexedDB.

Cómo trabajar con almacenes de objetos

Una base de datos IndexedDB contiene uno o más almacenes de objetos, y cada uno tiene un una columna para una clave y otra para los datos asociados con esa clave.

Crea almacenes de objetos

Una base de datos IndexedDB bien estructurada debe tener un almacén de objetos para cada tipo de datos que deben ser persistentes. Por ejemplo, en un sitio que conserva las credenciales los perfiles y las notas pueden tener un almacén de objetos people que contiene person objetos, y un almacén de objetos notes que contiene objetos note.

Para garantizar la integridad de la base de datos, solo puedes crear o quitar almacenes de objetos en eventos en una llamada a openDB(). El objeto de eventos expone un upgrade(). que te permite crear almacenes de objetos. Llama al 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 una configuración opcional que te permite definir varias propiedades para el almacén de objetos.

El siguiente es 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, se completa el trabajo de crearlo en el método upgrade() del objeto de evento. Sin embargo, como el navegador genera una si intentas crear un almacén de objetos que ya existe, te recomendamos uniendo 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

Al definir almacenes de objetos, puedes definir cómo se identifican los datos de forma única en a la tienda usando una clave primaria. Para definir una clave primaria, puedes definir un con la ruta de acceso de la clave o con un generador de claves.

Una ruta de la clave es una propiedad que siempre existe y contiene un valor único. Para Por ejemplo, en el caso de un almacén de objetos people, puedes elegir el 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 el email como 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 clave y la almacena por separado. de los datos.

En el siguiente ejemplo, se crea un almacén de objetos llamado 'notes' y se establece la la clave primaria se asignará automáticamente como un 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();

El siguiente ejemplo es similar al ejemplo 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();

Elegir el método que se usará para definir la clave depende de tus datos. Si el datos tienen una propiedad que siempre es única, puedes hacer que sea la keyPath aplicar esta singularidad. De lo contrario, usa un valor de incremento automático.

El siguiente código crea tres almacenes de objetos que demuestran las distintas formas de definir las claves primarias en 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 de la referencia de objetos de una propiedad especificada. Un índice reside dentro del objeto de referencia y contiene los mismos datos, pero usa la propiedad especificada como su la ruta de acceso de la clave en lugar de la clave primaria del almacén de referencia. Los índices deben hacerse cuando crea tus almacenes de objetos y se puede usar para definir una restricción única en tus datos.

Para crear un índice, llama a 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 muestra 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 El segundo argumento se refiere a la propiedad de los datos que quieres índice. El argumento final te permite definir dos opciones que determinan cómo opera el siguiente índice: unique y multiEntry. Si unique se configura como true, 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 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 crea los índices, primero asigna el resultado de createObjectStore() (un objeto store) a una variable para que puedas llamar a createIndex() en ella.

Cómo trabajar con datos

En esta sección, se describe cómo crear, leer, actualizar y borrar datos. Estos las operaciones son todas asíncronas, con promesas en las que la API de IndexedDB usa solicitudes. Esto simplifica la API. En lugar de detectar los eventos activados por la solicitud, puedes llamar a .then() en el objeto de la base de datos que muestra Método openDB() para iniciar interacciones con la base de datos, o await su de la creación de cuentas de servicio.

Todas las operaciones de datos en IndexedDB se llevan a cabo dentro de una transacción. Cada El formato es el siguiente:

  1. Obtén un objeto de base de datos.
  2. Transacción abierta en la base de datos.
  3. Abrir el depósito de objetos en la transacción
  4. Realizar la operación en el almacén de objetos

Una transacción puede considerarse como un wrapper seguro en torno a una operación o grupo de las operaciones. Si una de las acciones de una transacción falla, todas las acciones se revierten. 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 escribir. Esto indica si las operaciones dentro de la transacción leen el datos o hacer cambios en la base de datos.

Crea datos

Para crear datos, llama a add(). en la instancia de base de datos y pasa los datos que desees agregar. El add() El primer argumento de este método es el almacén de objetos al que quieres agregar los datos. El segundo argumento es un objeto que contiene los campos y los datos asociados que quieres para agregar. Este es el ejemplo más simple, 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() ocurre dentro de una transacción, de modo que incluso si se resuelve la promesa con éxito, no significa necesariamente que la operación funcionó. Para asegurarte se realizó la operación de adición, debes verificar si se completó la transacción con el método transaction.done(). Este es un prometida que se resuelve cuando la transacción se completa por sí misma, y se rechaza si errores de transacción. Debes realizar esta comprobación para toda la “escritura” operaciones, porque es la única forma de saber que los cambios en la base de datos de que ocurra.

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

Cuando abras la base de datos (y crees un almacén de objetos si es necesario), necesitarás Para abrir una transacción, llama al método transaction(). Este método toma un argumento para la tienda en la que quiere realizar la transacción, además del modo. En este caso, queremos escribirle a la tienda. En 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, tratamos con tres operaciones en 'foods' almacenar que cada una muestra una promesa:

  1. Agrega un récord para un sándwich sabroso.
  2. Se está agregando el registro de algunos huevos.
  3. Indica que se completó la transacción (tx.done).

Debido a que todas estas acciones se basan en promesas, debemos esperar a que para que terminen. Pasar estas promesas a Promise.all es una forma cómoda y ergonómica de hacer esto. Promise.all acepta un array de promete y finaliza cuando se resuelven todas las promesas que se le pasa.

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 de Promise.all para que termine cuando se complete la transacción.

Lee datos

Para leer datos, llama a get(). en la instancia de base de datos que recuperas con el método openDB(). get() toma el nombre de la tienda y el valor de clave primaria del objeto que deseas recuperar. A continuación, te mostramos 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() muestra una promesa, por lo que puedes await si que prefieras, o usa la devolución de llamada .then() de la promesa.

En el siguiente ejemplo, se usa el método get() en la base de datos 'test-db4' Un almacén de objetos 'foods' para obtener una sola fila por 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: open a la base de datos y especifica el almacén de objetos y el valor de clave primaria de la fila de las que quieres obtener datos. Debido a que el método get() muestra una promesa, puedes await.

Cómo actualizar datos

Para actualizar los datos, llama a 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 muestra una promesa. También puedes usar put() como parte de una transacción. Este es un ejemplo que usa la tienda 'foods' anterior que actualiza 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 estableces una clave. Si estableces un keyPath, Cada fila del almacén de objetos está asociada con una clave de intercalado. La anterior ejemplo actualiza las filas en función de esta clave y, cuando actualizas las filas de esta actual, deberás especificar esa clave para actualizar el elemento apropiado en la de un almacén de objetos. También puedes crear una clave fuera de línea estableciendo un autoIncrement como clave primaria

Borra datos

Para borrar datos, llama a 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 de la base de datos es la misma que la del otro las operaciones. Recuerda verificar que toda la transacción se haya completado antes del incluido el método tx.done en el array que pasas a Promise.all.

Obteniendo todos los datos

Hasta ahora, solo recuperaste objetos de la tienda uno a la vez. También puedes recuperar todos los datos, o un subconjunto, de un almacén de objetos o í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 í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 muestra todos los objetos del almacén de objetos, sin restricciones. en absoluto. 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 según la clave primaria

Cómo usar los cursores

Los cursores son una forma más flexible de recuperar múltiples objetos. Un cursor selecciona cada objeto en un índice o almacén de objetos, uno por uno, lo que te permite hacer algo con los datos cuando se seleccionen. Los cursores, como las otras operaciones de base de datos, funcionan en las transacciones.

Para crear un cursor, llama a openCursor(). en el almacén de objetos como parte de una transacción. Usando la tienda 'foods' de ejemplos anteriores, esta es la manera de hacer avanzar un cursor por todas las filas de datos en en 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 su openCursor. En un bucle while posterior, la fila en el la posición actual del cursor puede hacer que se lean las propiedades key y value, y puede operar con esos valores de la manera que le resulte más conveniente para su . Cuando esté todo listo, puedes llamar al continue() del objeto cursor. método para ir a la siguiente fila, y el bucle while termina cuando el cursor llega al final del conjunto de datos.

Usa cursores con índices y rangos

Los índices te permiten recuperar los datos en un depósito de objetos de una propiedad que no sea la clave primaria. Puedes crear un índice en cualquier propiedad, que se convierte en keyPath. para el índice, especificar un rango en esa propiedad y obtener los datos dentro del rango usando getAll() o un cursor.

Define tu rango con el objeto IDBKeyRange. y cualquiera de los siguientes métodos:

Los métodos upperBound() y lowerBound() especifican los límites inferior y superior del rango.

IDBKeyRange.lowerBound(indexKey);

o:

IDBKeyRange.upperBound(indexKey);

Cada una toma un argumento: el valor keyPath del índice para el elemento que deseas. especificar como el límite superior o inferior.

El método bound() especifica un límite inferior y superior:

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 objeto 'foods' en una tienda física. Ahora, la tienda tiene un formulario adjunto con dos entradas para el límites superior e inferior del rango. Usa el siguiente código para encontrar alimentos con entre estos 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);

El código de ejemplo primero obtiene los valores de los límites y comprueba si estos existen. El siguiente bloque de código decide qué método usar para limitar el rango según los valores. En la interacción de la base de datos, abre el almacén de objetos en como de costumbre y, luego, abre el índice 'price' en el almacén de objetos. El 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 en el rango, o undefined si no hay datos dentro del rango. El método cursor.continue() devuelve un que representa el siguiente objeto y continúa en el bucle hasta que llegar al final del rango.

Control de versiones de bases 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 configurado en 1, pero una base de datos se puede actualizar a una versión nueva si lo necesitas modificarlo de alguna manera. Si la versión especificada es superior a 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 índices y almacenes de objetos 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 la versión de la base de datos existente de la fila. 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 primero, 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, esta 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 case pero esto no se usa aquí deliberadamente. De esta manera, si el estado base de datos tiene algunas versiones anteriores o, si no existe, el código continúa por el resto de los bloques case hasta que esté actualizado. En el ejemplo, El navegador continúa ejecutándose a través de case 1, lo que 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 archivo 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 ejecuta, oldVersion es 2. El navegador omite case 0 y case 1 y ejecuta el código en case 2, que crea un description índice. Después de eso, el navegador tendrá una base de datos en la versión 3 que contiene un store. un almacén de objetos con índices name y description.

Lecturas adicionales

Los siguientes recursos proporcionan más información y contexto para usar IndexedDB.

Documentación de IndexedDB

Límites de almacenamiento de datos