Aller au contenu

IndexedDB : ouvrir une base et définir le schéma

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

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

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(...) dans upgradeneeded : utiliser requete.transaction.
  • Les migrations lourdes (millions d’enregistrements) bloquent l’ouverture : les prévoir en plusieurs étapes ou en tâche de fond.

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 »).

src/lib/db.js
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.

const demande = indexedDB.deleteDatabase('boutique');
demande.onsuccess = () => console.log('supprimée');
demande.onblocked = () => console.warn('Fermer les connexions ouvertes');
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.

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.