LEARNING OBJECTIVES ⌵
- Master the database opening workflow with
window.indexedDB.open(name, version). - Understand the role and execution guarantees of the
onupgradeneededlifecycle event. - Implement robust version migration scripts supporting step-by-step schema evolution.
- Handle multi-tab upgrade contention using
onversionchangeandonblockedevent hooks.
🎬 INTERACTIVE VISUAL PIPELINE
Core Architecture Simulation
1. Input
Directives & Tags
2. Parse
Tokenizer & AST
3. Layout
Box Model & Flow
4. Render
GPU Paint & Composite
PHASE 1: INPUT & DIRECTIVES
Browser receives declarative markup stream, parsing tag tokens and initializing component state.
📖 The Mental Model & Story (Intuitive Foundation)
Imagine an apartment building that needs a structural renovation—say, adding a new elevator shaft (a schema upgrade).
- The Architecture Blueprint (
indexedDB.open(name, version)): When you file a building permit with a higher version number (e.g., upgrading from Version 1 to Version 2), the city inspection team checks whether any residents are currently inside the building. - The Construction Zone (
onupgradeneeded): If the building permit is approved, the construction crew halts normal tenant activity and opens an exclusive construction phase. In this special window, you are allowed to knock down walls (delete old object stores), build new rooms (create new object stores), or install directory boards (create indexes). Once construction is finished, the zone is sealed. - The Occupancy Certificate (
onsuccess): Once the dust clears, tenants (your application's read/write transactions) are welcomed back in to conduct normal daily business. - The Squatter Conflict (
onblocked&onversionchange): If another browser tab is currently sitting open with an older Version 1 lease, the construction crew cannot start. The new tab receives anonblockedwarning, and the old tab receives anonversionchangenotice telling it: "Please close your connection immediately so the renovation can begin!"
Technical Deep Dive & Specifications
The indexedDB.open() State Machine
In IndexedDB, schema alterations (creating or deleting ObjectStores and Indexes) can only occur inside a dedicated versionchange transaction triggered during onupgradeneeded.
indexedDB.open(name, version)
|
v
+-------------------------------+
| Database exists & requested |
| version > current version? |
+-------------------------------+
/ \
YES / \ NO (Version matches)
v v
+--------------------+ +--------------------+
| onupgradeneeded | | onsuccess |
| (Schema Migration) | | (Ready for CRUD) |
+--------------------+ +--------------------+
|
v (Migration completed)
+--------------------+
| onsuccess |
| (Ready for CRUD) |
+--------------------+
Versioning Rules & Constraints
- Version format: Database versions must be unsigned positive integers (e.g.,
1,2,3). You cannot use decimals (1.5), strings ("2.0"), or negative numbers (-1). Passing0or a float throws aTypeError. - Implicit Version 1: If you call
indexedDB.open("myDB")without a version parameter and the database does not exist, the browser opens it with version1. - Downgrades Forbidden: If the existing database on disk is at version
3and you callindexedDB.open("myDB", 2), the open request immediately fires anonerrorevent with aVersionErrorDOMException.
Multi-Tab Upgrade Coordination
Because an IndexedDB database is shared across all tabs and workers of the same origin, version upgrades require coordination across active connections:
+------------------------------------+ +------------------------------------+
| TAB A | | TAB B |
| (Holds open DB Connection v1) | | (Requests DB Upgrade to v2) |
+------------------------------------+ +------------------------------------+
| |
| |-- indexedDB.open("app", 2)
| |
|<==== onversionchange fired ====================| (Tab B is waiting...)
| |
| |-- onblocked fired!
| (Tab A must call db.close()) | (If Tab A doesn't close)
| |
|-- db.close() |
| |
|===============================================>| (Lock released!)
| |
| |-- onupgradeneeded fires
| |-- onsuccess fires
- Tab B calls
open("app", 2). - The browser fires
db.onversionchangeon Tab A's existing database instance. - If Tab A immediately calls
db.close(), Tab B proceeds toonupgradeneeded. - If Tab A fails to close its connection, Tab B's open request fires
request.onblocked, warning the user or delaying the migration until Tab A is closed.
💻 Interactive Code Playground
Starter Code
Save this file as lifecycle.html and open it in your browser.
Line-by-Line Code Breakdown
- Line 57 (
const request = indexedDB.open(DB_NAME, version)): Initiates an asynchronous request to open the database. Returns anIDBOpenDBRequestinstance. - Lines 59–74 (
request.onupgradeneeded): Fires if the database is being created for the first time or if the requestedversionis strictly higher than the database version stored on disk. - Lines 67–73 (
if (oldVer < 1) ... if (oldVer < 2)): The canonical pattern for sequential, incremental database migrations without destroying existing tenant data. - Lines 76–87 (
request.onsuccess): Fires once the database is fully open and ready for normal read/write transactions. - Lines 81–86 (
currentDb.onversionchange): Hooks into the connection to receive eviction notices when another tab requests an upgrade. Closing the database immediately unblocks the requesting tab. - Lines 89–91 (
request.onblocked): Handles the condition where another tab is holding the database open and refuses to disconnect. - Lines 105–112 (
indexedDB.deleteDatabase(DB_NAME)): Permanently wipes the database and all its ObjectStores from disk storage.
Expected Browser Render Output
(If you then click "Upgrade to Version 2"):
[02:35:10] ▶️ Requesting connection to "LifecycleDemoDB" at version 1...
[02:35:10] 🛠️ onupgradeneeded: Migrating from version 0 ➔ 1
[02:35:10] Creating ObjectStore: "users" (keyPath: "id")
[02:35:10] ✅ onsuccess: Connected to "LifecycleDemoDB" (Active Version: 1)[02:35:15] ▶️ Requesting connection to "LifecycleDemoDB" at version 2...
[02:35:15] ⚠️ onversionchange: Another tab wants to upgrade this database. Closing our connection...
[02:35:15] 🔒 Connection closed gracefully.
[02:35:15] 🛠️ onupgradeneeded: Migrating from version 1 ➔ 2
[02:35:15] Creating ObjectStore: "settings" (autoIncrement: true)
[02:35:15] ✅ onsuccess: Connected to "LifecycleDemoDB" (Active Version: 2)🏋️ Hands-On Exercise
🎯 The Challenge: Resilient Multi-Version Migration Runner
Instructions:
- Construct an IndexedDB helper function
initStore(targetVersion)that connects to a database namedEnterpriseStore. - Implement schema migrations across 3 versions:
- Version 1: Creates store
"products"with{ keyPath: "sku" }. - Version 2: Creates store
"orders"with{ keyPath: "orderId" }. - Version 3: Deletes the deprecated
"products"store and creates"inventory"with{ keyPath: "itemId", autoIncrement: true }.
- Version 1: Creates store
- Add full error handling for version downgrades, connection blocking, and database deletion.
🏁 Starter Code Sandbox
⚠️ Common Pitfalls
- Creating ObjectStores outside
onupgradeneeded: Callingdb.createObjectStore()insideonsuccessthrows anInvalidStateError: Failed to execute 'createObjectStore' on 'IDBDatabase': The database is not running a version change transaction. - Forgetting to Handle
onversionchange: If your app does not attach adb.onversionchangelistener, a user opening a newly deployed version of your app in a second tab will hang indefinitely inonblocked. - Passing Float Versions: Executing
indexedDB.open("db", 2.1)rounds or throws errors depending on the engine. Always increment version numbers as pure integers (1,2,3).
💡 Pro Tips
- Never Downgrade Schemas in Production: IndexedDB strictly rejects version decreases. If a breaking schema bug occurs, push an emergency release that increments to version
N + 1with a repair migration script. - Track Migration History: For enterprise offline apps, store a
schema_migrationsrecord within IndexedDB containing timestamps, previous version snapshots, and migration diagnostics for telemetry reporting.
📌 Key Takeaways
- Schema modifications can only take place inside the
onupgradeneededlifecycle handler. - Versions must be positive non-zero integers (
1, 2, 3...); downgrading versions throws aVersionError. - Multi-step schema upgrades should use cumulative
if (oldVersion < X)conditional blocks. - Every active database instance should register an
onversionchangehandler to close its connection when another tab upgrades the schema. - The
onblockedhandler alerts the application when an upgrade cannot proceed due to lingering open connections in other tabs. - --
Question 1 / 3
What happens if a database is currently at Version 3, and a script executes indexedDB.open('myDB', 2)?
Topic: HTML Fundamentals
Question 2 / 3
Where is the ONLY valid place in JavaScript to execute db.createObjectStore() or db.deleteObjectStore()?
Topic: HTML Fundamentals
Question 3 / 3
What is the purpose of the db.onversionchange event listener on an active IDBDatabase connection?
Topic: HTML Fundamentals