Utilizza IndexedDB

Questa guida illustra le nozioni di base dell'API IndexedDB. Stiamo utilizzando la libreria IndexedDB Promised di Jake Archibald, molto simile all'API IndexedDB, ma che utilizza promesse, che puoi await per una sintassi più concisa. Ciò semplifica l'API mantenendo la struttura.

Che cos'è IndexedDB?

IndexedDB è un sistema di archiviazione NoSQL su larga scala, che consente di archiviare praticamente qualsiasi cosa nel browser dell'utente. Oltre alle consuete azioni di ricerca, get e put, IndexedDB supporta anche le transazioni ed è ideale per archiviare grandi quantità di dati strutturati.

Ogni database IndexedDB è univoco per un'origine (in genere il dominio o il sottodominio del sito), il che significa che non può accedere o essere accessibile da qualsiasi altra origine. I suoi limiti di archiviazione dei dati sono generalmente ampi, ove esistano, ma browser diversi gestiscono i limiti e l'eliminazione dei dati in modo diverso. Consulta la sezione Ulteriori informazioni per ulteriori informazioni.

Termini IndexedDB

Database
Il livello più alto di IndexedDB. Contiene gli archivi di oggetti, che a loro volta contengono i dati che vuoi mantenere. Puoi creare più database con qualsiasi nome tu scelga.
Archivio di oggetti
Un singolo bucket per l'archiviazione dei dati, in modo simile alle tabelle nei database relazionali. In genere, è presente un archivio di oggetti per ogni tipo (non per tipo di dati JavaScript) di dati che stai archiviando. A differenza delle tabelle di database, i tipi di dati JavaScript dei dati in un datastore non devono essere coerenti. Ad esempio, se un'app ha un archivio di oggetti people contenente informazioni su tre persone, le proprietà relative all'età di queste persone potrebbero essere 53, 'twenty-five' e unknown.
Indice
Un tipo di archivio di oggetti per organizzare i dati in un altro archivio di oggetti (denominato archivio di oggetti di riferimento) in base a una singola proprietà dei dati. L'indice viene utilizzato per recuperare i record nell'archivio di oggetti da questa proprietà. Ad esempio, se vuoi salvare delle persone, potresti volerle recuperare in un secondo momento indicando il loro nome, la loro età o l'animale preferito.
Operazione
Un'interazione con il database.
Transazione
Un wrapper attorno a un'operazione o a un gruppo di operazioni che garantisce l'integrità del database. Se una delle azioni in una transazione non va a buon fine, nessuna delle azioni viene applicata e il database torna allo stato in cui si trovava prima dell'inizio della transazione. Tutte le operazioni di lettura o scrittura in IndexedDB devono far parte di una transazione. Ciò consente le operazioni di lettura, modifica e scrittura atomiche senza il rischio di conflitti con altri thread che agiscono contemporaneamente sul database.
Cursore
Un meccanismo per eseguire l'iterazione su più record di un database.

Come verificare il supporto di IndexedDB

IndexedDB è quasi supportato universalmente. Tuttavia, se utilizzi browser meno recenti, non è una cattiva idea utilizzare il supporto del rilevamento delle funzionalità per ogni evenienza. Il modo più semplice è controllare l'oggetto 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();

Come aprire un database

Con IndexedDB, puoi creare più database con qualsiasi nome tu scelga. Se un database non esiste quando tenti di aprirlo, viene creato automaticamente. Per aprire un database, utilizza il metodo openDB() dalla libreria 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();

Questo metodo restituisce una promessa che si risolve in un oggetto del database. Quando utilizzi il metodo openDB(), fornisci un nome, un numero di versione e un oggetto eventi per configurare il database.

Ecco un esempio del metodo openDB() nel contesto:

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

Seleziona il controllo del supporto IndexedDB nella parte superiore della funzione anonima. In questo modo esce dalla funzione se il browser non supporta IndexedDB. Se la funzione può continuare, chiama il metodo openDB() per aprire un database denominato 'test-db1'. In questo esempio, l'oggetto eventi facoltativo è stato tralasciato per semplificare le cose, ma devi specificarlo per svolgere qualsiasi lavoro significativo con IndexedDB.

Come utilizzare gli archivi di oggetti

Un database IndexedDB contiene uno o più archivi di oggetti, ciascuno con una colonna per una chiave e un'altra colonna per i dati associati a quella chiave.

Creazione di archivi di oggetti

Un database IndexedDB ben strutturato deve avere un archivio di oggetti per ogni tipo di dati che deve essere mantenuto. Ad esempio, un sito che memorizza profili utente e note potrebbe avere un archivio di oggetti people contenente person oggetti e un archivio di oggetti notes contenente note oggetti.

Per garantire l'integrità del database, puoi creare o rimuovere archivi di oggetti solo nell'oggetto Eventi in una chiamata openDB(). L'oggetto event espone un metodo upgrade() che consente di creare archivi di oggetti. Chiama il metodo createObjectStore() all'interno del metodo upgrade() per creare l'archivio di oggetti:

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

Questo metodo prende il nome dell'archivio di oggetti e un oggetto di configurazione facoltativo che consente di definire varie proprietà per l'archivio di oggetti.

Di seguito è riportato un esempio di come utilizzare 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();

In questo esempio, un oggetto eventi viene passato al metodo openDB() per creare l'archivio di oggetti e, come prima, il lavoro di creazione dell'archivio di oggetti viene eseguito nel metodo upgrade() dell'oggetto evento. Tuttavia, poiché il browser genera un errore se provi a creare un archivio di oggetti già esistente, ti consigliamo di eseguire il wrapping del metodo createObjectStore() in un'istruzione if che verifica se l'archivio di oggetti esiste. All'interno del blocco if, chiama createObjectStore() per creare un archivio di oggetti denominato 'firstOS'.

Come definire le chiavi primarie

Quando definisci gli archivi di oggetti, puoi definire in che modo i dati vengono identificati in modo univoco nel datastore utilizzando una chiave primaria. Puoi definire una chiave primaria definendo un percorso chiave o utilizzando un generatore di chiavi.

Un percorso chiave è una proprietà sempre esistente e contenente un valore univoco. Ad esempio, nel caso di un archivio di oggetti people, potresti scegliere l'indirizzo email come percorso chiave:

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

Questo esempio crea un archivio di oggetti denominato 'people' e assegna la proprietà email come chiave primaria nell'opzione keyPath.

Puoi anche utilizzare un generatore di chiavi come autoIncrement. Il generatore di chiavi crea un valore univoco per ogni oggetto aggiunto all'archivio di oggetti. Per impostazione predefinita, se non specifichi una chiave, IndexedDB crea una chiave e la archivia separatamente dai dati.

L'esempio seguente crea un archivio di oggetti denominato 'notes' e imposta la chiave primaria da assegnare automaticamente come numero con incremento automatico:

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

L'esempio seguente è simile all'esempio precedente, ma questa volta il valore con incremento automatico viene assegnato esplicitamente a una proprietà denominata '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 scelta del metodo da utilizzare per definire la chiave dipende dai dati. Se i tuoi dati hanno una proprietà sempre univoca, puoi impostarla come keyPath per applicare questa univocità. Altrimenti, utilizza un valore che incrementa automaticamente.

Il codice seguente crea tre archivi di oggetti che mostrano i vari modi di definire le chiavi primarie negli archivi di oggetti:

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

Come definire gli indici

Gli indici sono un tipo di archivio di oggetti utilizzato per recuperare dati dall'archivio di oggetti di riferimento da parte di una proprietà specificata. Un indice si trova all'interno dell'archivio degli oggetti di riferimento e contiene gli stessi dati, ma utilizza la proprietà specificata come percorso chiave anziché la chiave primaria dell'archivio di riferimento. Gli indici devono essere creati durante la creazione degli archivi di oggetti e possono essere utilizzati per definire un vincolo univoco sui dati.

Per creare un indice, chiama il metodo createIndex() su un'istanza dell'archivio di oggetti:

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

Questo metodo crea e restituisce un oggetto indice. Il metodo createIndex() nell'istanza dell'archivio di oggetti prende il nome del nuovo indice come primo argomento, mentre il secondo argomento si riferisce alla proprietà sui dati che vuoi indicizzare. L'argomento finale consente di definire due opzioni che determinano il funzionamento dell'indice: unique e multiEntry. Se unique è impostato su true, l'indice non consente valori duplicati per una singola chiave. Poi, multiEntry determina il comportamento di createIndex() quando la proprietà indicizzata è un array. Se è impostato su true, createIndex() aggiunge una voce nell'indice per ogni elemento dell'array. In caso contrario, viene aggiunta una singola voce contenente l'array.

Esempio:

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

In questo esempio, gli archivi di oggetti 'people' e 'notes' hanno indici. Per creare gli indici, assegna prima il risultato di createObjectStore() (un oggetto dell'archivio oggetti) a una variabile in modo da poter chiamare createIndex().

Come utilizzare i dati

Questa sezione descrive come creare, leggere, aggiornare ed eliminare i dati. Queste operazioni sono tutte asincrone, utilizzando promesse in cui l'API IndexedDB utilizza le richieste. Ciò semplifica l'API. Anziché ascoltare gli eventi attivati dalla richiesta, puoi chiamare .then() sull'oggetto di database restituito dal metodo openDB() per avviare le interazioni con il database o awaitla sua creazione.

Tutte le operazioni sui dati in IndexedDB vengono eseguite all'interno di una transazione. Ogni operazione ha il seguente formato:

  1. Recupera oggetto di database.
  2. Apri transazione nel database.
  3. Apri l'archivio di oggetti al momento della transazione.
  4. Esegui un'operazione sull'archivio di oggetti.

Una transazione può essere considerata come un wrapper sicuro per un'operazione o un gruppo di operazioni. Se una delle azioni in una transazione ha esito negativo, viene eseguito il rollback di tutte le azioni. Le transazioni sono specifiche di uno o più archivi di oggetti, che definisci quando apri la transazione. Possono essere di sola lettura o di lettura e scrittura. Questo indica se le operazioni all'interno della transazione leggono i dati o apportano una modifica al database.

Creare i dati

Per creare i dati, chiama il metodo add() sull'istanza di database e trasmetti i dati che vuoi aggiungere. Il primo argomento del metodo add() è l'archivio di oggetti a cui vuoi aggiungere i dati e il secondo argomento è un oggetto che contiene i campi e i dati associati che vuoi aggiungere. Ecco l'esempio più semplice, in cui viene aggiunta una singola riga di dati:

import {openDB} from 'idb';

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

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

addItemToStore();

Ogni chiamata a add() avviene all'interno di una transazione, quindi, anche se la promessa si risolve in modo corretto, non significa necessariamente che l'operazione abbia funzionato. Per assicurarti che l'operazione di aggiunta sia stata eseguita, devi verificare se l'intera transazione è stata completata utilizzando il metodo transaction.done(). Si tratta di una promessa che si risolve quando la transazione viene completata da sola e, in caso di errori, viene rifiutata. Devi eseguire questo controllo per tutte le operazioni di "scrittura", poiché è l'unico modo per sapere che le modifiche al database sono effettivamente avvenute.

Il seguente codice mostra l'utilizzo del metodo add() in una transazione:

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 volta aperto il database (e creato un archivio di oggetti, se necessario), devi aprire una transazione chiamando il metodo transaction() sul database. Questo metodo prende un argomento per il negozio su cui vuoi eseguire le transazioni, nonché per la modalità. In questo caso ci interessa scrivere allo store, quindi questo esempio specifica 'readwrite'.

Il passaggio successivo consiste nell'iniziare ad aggiungere articoli al negozio come parte della transazione. Nell'esempio precedente, abbiamo a che fare con tre operazioni sullo store 'foods' che restituiscono ciascuna una promessa:

  1. Aggiungo un record per un delizioso panino.
  2. Aggiunta di un record per alcune uova.
  3. Segnale che la transazione è stata completata (tx.done).

Poiché tutte queste azioni sono basate su promesse, dobbiamo attendere che siano completate. Trasmettere queste promesse a Promise.all è un modo piacevole ed ergonomico per raggiungere questo obiettivo. Promise.all accetta un array di promesse e termina una volta che tutte le promesse sono state risolte.

Per i due record che vengono aggiunti, l'interfaccia store dell'istanza di transazione chiama add() e vi passa i dati. Puoi await la chiamata Promise.all in modo che finisca al termine della transazione.

Lettura di dati

Per leggere i dati, chiama il metodo get() sull'istanza di database che recupera usando il metodo openDB(). get() prende il nome dell'archivio e il valore della chiave primaria dell'oggetto che vuoi recuperare. Ecco un esempio di base:

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

Come per add(), il metodo get() restituisce una promessa, quindi puoi await, se preferisci, o utilizzare il callback .then() della promessa.

L'esempio seguente utilizza il metodo get() nell'archivio di oggetti 'foods' del database 'test-db4' per ottenere una singola riga tramite la chiave 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();

Il recupero di una singola riga dal database è piuttosto semplice: apri il database e specifica l'archivio oggetti e il valore della chiave primaria della riga da cui vuoi recuperare i dati. Poiché il metodo get() restituisce una promessa, puoi await questo metodo.

Aggiorna dati

Per aggiornare i dati, chiama il metodo put() nell'archivio oggetti. Il metodo put() è simile al metodo add() e può essere utilizzato anche al posto di add() per creare dati. Ecco un esempio di base dell'utilizzo di put() per aggiornare una riga in un archivio di oggetti in base al valore della chiave 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();

Come altri metodi, questo metodo restituisce una promessa. Puoi anche utilizzare put() nell'ambito di una transazione. Ecco un esempio utilizzando il negozio 'foods' di prima che aggiorna il prezzo del panino e delle uova:

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 modalità di aggiornamento degli elementi dipende da come imposti una chiave. Se imposti un valore keyPath, ogni riga nell'archivio di oggetti è associata a una chiave in linea. L'esempio precedente aggiorna le righe in base a questa chiave e, quando aggiorni le righe in questa situazione, devi specificare la chiave per aggiornare l'elemento appropriato nell'archivio oggetti. Puoi anche creare una chiave fuori linea impostando una chiave autoIncrement come chiave primaria.

Elimina i dati

Per eliminare i dati, chiama il metodo delete() nell'archivio oggetti:

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

Come add() e put(), puoi utilizzare queste informazioni come parte di una transazione:

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 struttura dell'interazione con il database è la stessa delle altre operazioni. Ricordati di verificare che l'intera transazione sia stata completata includendo il metodo tx.done nell'array che passi a Promise.all.

Recupero di tutti i dati

Finora hai recuperato solo gli oggetti dall'archivio uno alla volta. Puoi anche recuperare tutti i dati, o un sottoinsieme, da un archivio o indice di oggetti utilizzando il metodo getAll() o i cursori.

Il metodo getAll()

Il modo più semplice per recuperare tutti i dati di un archivio di oggetti è chiamare getAll() nell'archivio o sull'indice di oggetti, in questo modo:

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

Questo metodo restituisce tutti gli oggetti nell'archivio di oggetti, senza vincoli. È il modo più diretto per ottenere tutti i valori da un archivio di oggetti, ma anche il meno flessibile.

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

Questo esempio chiama getAll() nell'archivio di oggetti 'foods'. Vengono restituiti tutti gli oggetti da 'foods', ordinati in base alla chiave primaria.

Come usare i cursori

I cursori consentono di recuperare più oggetti in modo più flessibile. Un cursore seleziona singolarmente ogni oggetto in un archivio o indice di oggetti, consentendoti di utilizzare i dati quando vengono selezionati. I cursori, come le altre operazioni del database, funzionano nelle transazioni.

Per creare un cursore, chiama openCursor() nell'archivio oggetti come parte di una transazione. Utilizzando l'archivio 'foods' degli esempi precedenti, ecco come avanzare con il cursore tra tutte le righe di dati in un archivio di oggetti:

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

La transazione in questo caso viene aperta in modalità 'readonly' e viene richiamato il relativo metodo openCursor. In un ciclo while successivo, la riga nella posizione attuale del cursore può ricevere le proprietà key e value e puoi operare su quei valori nel modo più adatto alla tua app. Quando è tutto pronto, puoi chiamare il metodo continue() dell'oggetto cursor per passare alla riga successiva e il loop while termina quando il cursore raggiunge la fine del set di dati.

Utilizzare i cursori con intervalli e indici

Gli indici consentono di recuperare i dati in un archivio di oggetti da una proprietà diversa dalla chiave primaria. Puoi creare un indice su qualsiasi proprietà, che diventa keyPath per l'indice, specificare un intervallo su quella proprietà e ottenere i dati all'interno dell'intervallo utilizzando getAll() o un cursore.

Definisci l'intervallo utilizzando l'oggetto IDBKeyRange e uno dei seguenti metodi:

I metodi upperBound() e lowerBound() specificano i limiti superiore e inferiore dell'intervallo.

IDBKeyRange.lowerBound(indexKey);

Oppure:

IDBKeyRange.upperBound(indexKey);

Ognuno dei due ha un argomento: il valore keyPath dell'indice per l'elemento che vuoi specificare come limite superiore o inferiore.

Il metodo bound() specifica un limite superiore e uno inferiore:

IDBKeyRange.bound(lowerIndexKey, upperIndexKey);

L'intervallo per queste funzioni è inclusivo per impostazione predefinita, il che significa che include i dati specificati come limiti dell'intervallo. Per escludere questi valori, specifica l'intervallo come esclusivo passando true come secondo argomento per lowerBound() o upperBound() oppure come terzo e quarto argomento di bound(), rispettivamente per il limite inferiore e il limite superiore.

L'esempio successivo utilizza un indice nella proprietà 'price' nell'archivio di oggetti 'foods'. All'archivio ora è associato anche un modulo con due input per il limite superiore e quello inferiore dell'intervallo. Usa il seguente codice per trovare alimenti con prezzi compresi tra questi limiti:

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

Il codice di esempio recupera innanzitutto i valori dei limiti e verifica se i limiti esistono. Il blocco di codice successivo determina il metodo da utilizzare per limitare l'intervallo in base ai valori. Nell'interazione con il database, apri l'archivio di oggetti nella transazione come di consueto, quindi apri l'indice 'price' nell'archivio di oggetti. L'indice 'price' ti consente di cercare articoli in base al prezzo.

Il codice apre un cursore sull'indice e passa all'interno dell'intervallo. Il cursore restituisce una promessa che rappresenta il primo oggetto dell'intervallo oppure undefined se non esistono dati nell'intervallo. Il metodo cursor.continue() restituisce un cursore che rappresenta l'oggetto successivo e continua nel loop fino a quando non raggiungi la fine dell'intervallo.

Controllo delle versioni del database

Quando chiami il metodo openDB(), puoi specificare il numero di versione del database nel secondo parametro. In tutti gli esempi in questa guida, la versione è stata impostata su 1, ma è possibile eseguire l'upgrade di un database a una nuova versione se devi modificarlo in qualche modo. Se la versione specificata è superiore a quella del database esistente, viene eseguito il callback upgrade nell'oggetto evento, consentendoti di aggiungere nuovi archivi e indici di oggetti al database.

L'oggetto db nel callback upgrade ha una proprietà oldVersion speciale, che indica il numero di versione del database a cui il browser ha accesso. Puoi passare questo numero di versione in un'istruzione switch per eseguire blocchi di codice all'interno del callback upgrade in base al numero di versione del database esistente. Esempio:

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

Questo esempio imposta la versione più recente del database su 2. Quando questo codice viene eseguito per la prima volta, il database non esiste ancora nel browser, pertanto oldVersion è 0 e l'istruzione switch inizia da case 0. Nell'esempio, viene aggiunto un archivio di oggetti 'store' al database.

Punto chiave: nelle istruzioni switch, di solito c'è un break dopo ogni blocco case, ma intenzionalmente non viene utilizzato qui. In questo modo, se il database esistente è indietro di alcune versioni o non esiste, il codice continua per il resto dei blocchi case fino a quando non è aggiornato. Nell'esempio, quindi, il browser continua a eseguire tramite case 1, creando un indice name nell'archivio di oggetti store.

Per creare un indice 'description' nell'archivio di oggetti 'store', aggiorna il numero di versione e aggiungi un nuovo blocco case come segue:

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 il database creato nell'esempio precedente esiste ancora nel browser, quando viene eseguito, oldVersion è 2. Il browser ignora case 0 e case 1 ed esegue il codice in case 2, creando un indice description. Dopodiché, il browser dispone di un database versione 3 contenente un archivio di oggetti store con indici name e description.

Per approfondire

Le seguenti risorse forniscono ulteriori informazioni e contesto per l'utilizzo di IndexedDB.

Documentazione di IndexedDB

Limiti di archiviazione dei dati