demo · v152

Schema migration

onupgradeneeded is IndexedDB's migration hook — it runs once per version bump inside an atomic transaction. The Chrome 152 SQLite backend makes these upgrades more reliable: if the migration aborts or the browser crashes mid-upgrade, the database rolls back to its prior version cleanly rather than leaving it in a partial state.

Walk through three schema versions: v1 (users store), v2 (add posts + full-name index), v3 (migrate data + remove legacy field). After each upgrade, run the integrity check to confirm all records survived and the schema is correct.

Starting point

v0

No database yet

Version 1

v1

users (id, name, email)

Version 2

v2

+ posts + fullName index

Version 3

v3

migrate name → firstName/lastName

Current schema:
— no stores —
Migration log:
Click "Migrate to v1" to start.
// Schema migration via onupgradeneeded — atomic per version
const request = indexedDB.open('showcase-demo', 3);

request.onupgradeneeded = (event) => {
  const db = event.target.result;
  const oldVersion = event.oldVersion;

  // Each case falls through to run all upgrades from current to target
  switch (oldVersion) {
    case 0:
      // v1: create users store
      const users = db.createObjectStore('users', { keyPath: 'id', autoIncrement: true });
      users.createIndex('email', 'email', { unique: true });
      // fall through

    case 1:
      // v2: add posts store and fullName index on users
      const posts = db.createObjectStore('posts', { keyPath: 'id', autoIncrement: true });
      posts.createIndex('authorId', 'authorId');
      // Modify existing store: add new index (data stays intact)
      const usersStore = event.target.transaction.objectStore('users');
      usersStore.createIndex('name', 'name', { unique: false });
      // fall through

    case 2:
      // v3: split 'name' into 'firstName'/'lastName'
      // Use the transaction to migrate records in-place
      const tx = event.target.transaction;
      const store = tx.objectStore('users');
      store.openCursor().onsuccess = (e) => {
        const cursor = e.target.result;
        if (!cursor) return;
        const record = cursor.value;
        const parts = (record.name || '').split(' ');
        record.firstName = parts[0] || '';
        record.lastName  = parts.slice(1).join(' ') || '';
        delete record.name;
        cursor.update(record);
        cursor.continue();
      };
      store.deleteIndex('name'); // remove old index
      store.createIndex('lastName', 'lastName');
      break;
  }
};

// Chrome 152 SQLite backend: if onupgradeneeded throws or browser crashes,
// the entire upgrade transaction rolls back — no partial schema states.

see also