IndexedDB : ouvrir une base et définir le schéma
Ouvrir une base
Section intitulée « Ouvrir une base »const requete = indexedDB.open('boutique', 2); // nom, version
requete.onupgradeneeded = (evenement) => { const db = requete.result; const ancienne = evenement.oldVersion; // 0 si la base n'existe pas encore // créer ou modifier le schéma ici};
requete.onsuccess = () => { const db = requete.result; db.onversionchange = () => { db.close(); }; // voir plus bas};
requete.onerror = () => console.error(requete.error);requete.onblocked = () => console.warn('Mise à jour bloquée par un autre onglet');open(nom, version) crée la base si elle n’existe pas. La version est un entier : augmenter la version déclenche upgradeneeded, seul endroit où l’on peut modifier le schéma (magasins, index).
Créer des magasins d’objets
Section intitulée « Créer des magasins d’objets »requete.onupgradeneeded = () => { const db = requete.result;
const clients = db.createObjectStore('clients', { keyPath: 'id', autoIncrement: true, }); clients.createIndex('email', 'email', { unique: true }); clients.createIndex('nom', 'nom');
const commandes = db.createObjectStore('commandes', { keyPath: 'numero' }); commandes.createIndex('clientId', 'clientId'); commandes.createIndex('date', 'date');};Option de createObjectStore |
Sens |
|---|---|
keyPath: 'id' |
La clé est le champ id de l’objet (« clé interne ») |
autoIncrement: true |
IndexedDB génère la clé (nombre croissant) |
| Aucune option | Clé « externe » : fournie à chaque écriture (store.put(valeur, cle)) |
| Type de clé | Exemples |
|---|---|
| Valides | Nombre, chaîne, Date, tableau de ces types, ArrayBuffer |
| Non valides | Booléen, objet simple, null |
Choisir une clé stable et unique : un identifiant généré (crypto.randomUUID()) convient mieux qu’un champ métier qui peut changer.
db.createObjectStore('notes', { keyPath: 'id' });// puis : store.add({ id: crypto.randomUUID(), titre: 'Idée' })Créer des index
Section intitulée « Créer des index »store.createIndex(nomIndex, cheminChamp, options);| Option | Sens |
|---|---|
unique: true |
Interdit deux enregistrements avec la même valeur (l’écriture échoue avec ConstraintError) |
multiEntry: true |
Pour un champ tableau : une entrée d’index par élément du tableau |
store.createIndex('etiquettes', 'etiquettes', { multiEntry: true }); // { etiquettes: ['a', 'b'] }store.createIndex('nomEtAge', ['nom', 'age']); // index composéstore.createIndex('ville', 'adresse.ville'); // champ imbriquéUn index n’indexe que les enregistrements dont le champ existe et est une clé valide.
Versions et migrations
Section intitulée « Versions et migrations »Le schéma évolue par versions successives. upgradeneeded s’exécute une fois pour passer de oldVersion à la nouvelle version : traiter chaque étape :
const VERSION = 3;
requete.onupgradeneeded = (e) => { const db = requete.result; const tx = requete.transaction; // transaction de mise à jour (versionchange) const ancienne = e.oldVersion;
if (ancienne < 1) { const clients = db.createObjectStore('clients', { keyPath: 'id', autoIncrement: true, }); clients.createIndex('email', 'email', { unique: true }); }
if (ancienne < 2) { db.createObjectStore('commandes', { keyPath: 'numero' }).createIndex( 'clientId', 'clientId', ); }
if (ancienne < 3) { // ajout d'un index sur un magasin existant : passer par la transaction de mise à jour tx.objectStore('clients').createIndex('nom', 'nom'); // migration de données const curseur = tx.objectStore('commandes').openCursor(); curseur.onsuccess = () => { const c = curseur.result; if (!c) return; c.update({ ...c.value, statut: c.value.statut ?? 'nouvelle' }); c.continue(); }; }};Règles :
- Ne jamais modifier une étape publiée : ajouter une nouvelle version.
- Chaque étape
if (ancienne < n)garantit qu’un utilisateur qui revient après plusieurs versions passe toutes les migrations. - Pour supprimer :
db.deleteObjectStore('ancien'),store.deleteIndex('ancien'). - Ne pas utiliser
db.transaction(...)dansupgradeneeded: utiliserrequete.transaction. - Les migrations lourdes (millions d’enregistrements) bloquent l’ouverture : les prévoir en plusieurs étapes ou en tâche de fond.
Plusieurs onglets : versionchange et blocked
Section intitulée « Plusieurs onglets : versionchange et blocked »Une mise à jour de schéma n’est possible que si toutes les connexions ouvertes à l’ancienne version sont fermées. Dans chaque connexion, écouter versionchange :
requete.onsuccess = () => { const db = requete.result; db.onversionchange = () => { db.close(); // libère la voie à la mise à jour alert('Une nouvelle version est disponible : recharger la page.'); };};Sans cette fermeture, l’onglet qui ouvre la nouvelle version reçoit l’événement blocked et attend. Informer l’utilisateur (« Fermer les autres onglets de l’application »).
Un module d’ouverture réutilisable
Section intitulée « Un module d’ouverture réutilisable »let promesse;
export function ouvrirBase() { promesse ??= new Promise((resolve, reject) => { const requete = indexedDB.open('boutique', 3); requete.onupgradeneeded = (e) => migrer(requete, e.oldVersion); requete.onsuccess = () => { const db = requete.result; db.onversionchange = () => { db.close(); promesse = undefined; }; db.onclose = () => { promesse = undefined; }; resolve(db); }; requete.onerror = () => { promesse = undefined; reject(requete.error); }; requete.onblocked = () => console.warn('Ouverture bloquée par un autre onglet'); }); return promesse;}Garder une seule connexion par onglet (mémorisée dans promesse) plutôt que d’en ouvrir une par opération.
Supprimer une base
Section intitulée « Supprimer une base »const demande = indexedDB.deleteDatabase('boutique');demande.onsuccess = () => console.log('supprimée');demande.onblocked = () => console.warn('Fermer les connexions ouvertes');Lister les bases
Section intitulée « Lister les bases »const bases = await indexedDB.databases(); // [{ name: 'boutique', version: 3 }]La méthode databases() n’est pas disponible dans tous les navigateurs : tester sa présence.
Dépannage
Section intitulée « Dépannage »| Symptôme | Piste |
|---|---|
NotFoundError sur transaction('x') |
Magasin non créé : schéma en retard, version non augmentée |
InvalidStateError: … not a version change transaction |
createObjectStore hors de upgradeneeded |
VersionError |
Version demandée inférieure à celle de la base existante |
| L’ouverture ne se termine pas | blocked : un autre onglet garde l’ancienne version ouverte |
ConstraintError à la création d’un index unique |
Des doublons existent déjà |
| Le schéma n’évolue pas | upgradeneeded s’exécute seulement si la version augmente |
DataError sur add |
Clé absente ou invalide (type non autorisé) |
Ubuntu 24.04 et 26.04 : différences à connaître
Section intitulée « Ubuntu 24.04 et 26.04 : différences à connaître »Aucune.