IndexedDB Transaction Lifecycle and the Limits of Commit Durability
An in-depth architectural analysis of IndexedDB transaction lifecycles, event-loop auto-commit behaviors, and strict vs relaxed storage durability modes.
Publication & corrections: infostoa · Editorial standards
Individual author and specialist reviewer information is not provided for this article.
IndexedDB transactions apply reads and writes atomically. The durability option is a hint about whether the browser should prioritize performance or durability at commit; it does not guarantee a particular physical device or recovery from failure.
The storage metaphor does not show IndexedDB transaction states, strict/relaxed/default choices, or an actual commit result.
Object stores and indexes
An object store holds records made of keys and values; keys are unique within that store. An index provides a lookup path through another field in the stored values. This logical model does not establish a browser’s disk layout or a universal capacity limit.
When a transaction can accept requests
The W3C specification distinguishes active, inactive, committing, and finished states. A transaction is active when created and while an associated request event is being dispatched; additional requests can be queued then. Once dispatch ends, it becomes inactive. The implementation attempts to commit an inactive transaction after its requests and result handling are complete, no new request has been placed, and it has not been aborted. Commit is atomic; failure follows the abort procedure, which rolls back that transaction’s changes. Do not assume a transaction remains active after waiting for a timer or network response. But calling setTimeout() or fetch() does not, by itself, always abort it—the state when a later database request is made matters.
Hypothetical example: open the transaction after the network response
Suppose an app fetches a draft and then saves it locally. Finish the network request and JSON parsing first; then create the transaction, enqueue put() synchronously, and wait for complete to confirm the browser reported transaction completion.
Assumptions: db is an already-open database; its drafts object store was created with keyPath: "id"; and the response object contains id.
function completed(tx) {
return new Promise((resolve, reject) => {
tx.addEventListener("complete", resolve, { once: true });
tx.addEventListener("abort", () => reject(tx.error ?? new Error("Transaction aborted")), { once: true });
});
}
async function saveDraft(db) {
const response = await fetch("/api/draft/42");
if (!response.ok) throw new Error("Draft request failed");
const draft = await response.json();
const tx = db.transaction("drafts", "readwrite");
const done = completed(tx);
tx.objectStore("drafts").put(draft);
await done;
}
This is an explanatory snippet and was not executed. Reversing the order—creating the transaction, awaiting fetch(), then calling put()—can fail because the transaction may become inactive or finish while control is away. The W3C specification says a transaction becomes inactive when control returns to the event loop and accepts no new requests in that state.
A second hypothetical race: network responses for edits 21 and 22 arrive in reverse order. Blindly writing on arrival lets the older revision 21 overwrite 22. Store a monotonically increasing server revision and compare it with the current value inside the same readwrite transaction; write only the newer revision. The expected result is that a late stale response cannot replace the latest stored value. Test this race and its outcome in the target application.
What `durability` changes
With strict, the user agent may consider the transaction committed after verifying that outstanding changes reached persistent storage. With relaxed, it may consider the transaction committed once changes are written to the operating system, without additional verification. default leaves the choice to the browser. These are permitted commit behaviors, not controls for choosing a disk-flush mechanism or guaranteeing survival of a power failure.
Choose the hint by distinguishing regenerable cache data from writes whose recent loss is costly, and check support where it is used. A transaction completion event reports completion according to the browser implementation; it is not a backup. Data needed after device loss or storage corruption needs another recovery path, such as synchronization or a separate backup. Do not promise permanent retention based on local storage alone.