This is the Third Edition of Indexed Database API. The First Edition, simply titled "Indexed Database API", became a W3C Recommendation on 8 January 2015. The Second Edition, titled "Indexed Database API 2.0", became a W3C Recommendation on 30 January 2018.
Indexed Database API 3.0 is intended to supersede Indexed Database API 2.0.
1. Introduction
User agents need to store large numbers of objects locally in order to satisfy off-line data requirements of Web applications. [WEBSTORAGE] is useful for storing pairs of keys and their corresponding values. However, it does not provide in-order retrieval of keys, efficient searching over values, or storage of duplicate values for a key.
This specification provides a concrete API to perform advanced key-value data management that is at the heart of most sophisticated query processors. It does so by using transactional databases to store keys and their corresponding values (one or more per key), and providing a means of traversing keys in a deterministic order. This is often implemented through the use of persistent B-tree data structures that are considered efficient for insertion and deletion as well as in-order traversal of very large numbers of data records.
"library"
database. It has a "books"
object store that holds books records stored by their "isbn"
property as the primary key.
Book records have a "title"
property. This example artificially requires that book titles are unique. The code enforces this by creating an index named "by_title"
with the unique
option set. This index is used to look up books by title, and will prevent adding books with non-unique titles.
Book records also have an "author"
property, which is not required to be unique. The code creates another index named "by_author"
to allow look-ups by this property.
The code first opens a connection to the database. The upgradeneeded
event handler code creates the object store and indexes, if needed. The success
event handler code saves the opened connection for use in later examples.
const request= indexedDB. open( "library" ); let db; request. onupgradeneeded= function () { // The database did not previously exist, so create object stores and indexes. const db= request. result; const store= db. createObjectStore( "books" , { keyPath: "isbn" }); const titleIndex= store. createIndex( "by_title" , "title" , { unique: true }); const authorIndex= store. createIndex( "by_author" , "author" ); // Populate with initial data. store. put({ title: "Quarry Memories" , author: "Fred" , isbn: 123456 }); store. put({ title: "Water Buffaloes" , author: "Fred" , isbn: 234567 }); store. put({ title: "Bedrock Nights" , author: "Barney" , isbn: 345678 }); }; request. onsuccess= function () { db= request. result; };
The following example populates the database using a transaction.
const tx= db. transaction( "books" , "readwrite" ); const store= tx. objectStore( "books" ); store. put({ title: "Quarry Memories" , author: "Fred" , isbn: 123456 }); store. put({ title: "Water Buffaloes" , author: "Fred" , isbn: 234567 }); store. put({ title: "Bedrock Nights" , author: "Barney" , isbn: 345678 }); tx. oncomplete= function () { // All requests have succeeded and the transaction has committed. };
The following example looks up a single book in the database by title using an index.
const tx= db. transaction( "books" , "readonly" ); const store= tx. objectStore( "books" ); const index= store. index( "by_title" ); const request= index. get( "Bedrock Nights" ); request. onsuccess= function () { const matching= request. result; if ( matching!== undefined ) { // A match was found. report( matching. isbn, matching. title, matching. author); } else { // No match was found. report( null ); } };
The following example looks up all books in the database by author using an index and a cursor.
const tx= db. transaction( "books" , "readonly" ); const store= tx. objectStore( "books" ); const index= store. index( "by_author" ); const request= index. openCursor( IDBKeyRange. only( "Fred" )); request. onsuccess= function () { const cursor= request. result; if ( cursor) { // Called for each matching record. report( cursor. value. isbn, cursor. value. title, cursor. value. author); cursor. continue (); } else { // No more matching records. report( null ); } };
The following example shows one way to handle errors when a request fails.
const tx= db. transaction( "books" , "readwrite" ); const store= tx. objectStore( "books" ); const request= store. put({ title: "Water Buffaloes" , author: "Slate" , isbn: 987654 }); request. onerror= function ( event) { // The uniqueness constraint of the "by_title" index failed. report( request. error); // Could call event.preventDefault() to prevent the transaction from aborting. }; tx. onabort= function () { // Otherwise the transaction will automatically abort due the failed request. report( tx. error); };
The database connection can be closed when it is no longer needed.
db. close();
In the future, the database might have grown to contain other object stores and indexes. The following example shows one way to handle migrating from an older version.
const request= indexedDB. open( "library" , 3 ); // Request version 3. let db; request. onupgradeneeded= function ( event) { const db= request. result; if ( event. oldVersion< 1 ) { // Version 1 is the first version of the database. const store= db. createObjectStore( "books" , { keyPath: "isbn" }); const titleIndex= store. createIndex( "by_title" , "title" , { unique: true }); const authorIndex= store. createIndex( "by_author" , "author" ); } if ( event. oldVersion< 2 ) { // Version 2 introduces a new index of books by year. const bookStore= request. transaction. objectStore( "books" ); const yearIndex= bookStore. createIndex( "by_year" , "year" ); } if ( event. oldVersion< 3 ) { // Version 3 introduces a new object store for magazines with two indexes. const magazines= db. createObjectStore( "magazines" ); const publisherIndex= magazines. createIndex( "by_publisher" , "publisher" ); const frequencyIndex= magazines. createIndex( "by_frequency" , "frequency" ); } }; request. onsuccess= function () { db= request. result; // db.version will be 3. };
upgradeneeded
event), it cannot do so until all other clients close their connection to the
current version of the database.
To avoid blocking a new client from upgrading, clients can listen for the
versionchange
event. This fires when another client is wanting to upgrade the
database. To allow this to continue, react to the versionchange
event by doing
something that ultimately closes this client’s connection to the database.
One way of doing this is to reload the page:
db. onversionchange= function () { // First, save any unsaved data: saveUnsavedData(). then( function () { // If the document isn't being actively used, it could be appropriate to reload // the page without the user's interaction. if ( ! document. hasFocus()) { location. reload(); // Reloading will close the database, and also reload with the new JavaScript // and database definitions. } else { // If the document has focus, it can be too disruptive to reload the page. // Maybe ask the user to do it manually: displayMessage( "Please reload this page for the latest version." ); } }); }; function saveUnsavedData() { // How you do this depends on your app. } function displayMessage() { // Show a non-modal message to the user. }
Another way is to call the connection’s close()
method. However, you need to make
sure your app is aware of this, as subsequent attempts to access the database
will fail.
db. onversionchange= function () { saveUnsavedData(). then( function () { db. close(); stopUsingTheDatabase(); }); }; function stopUsingTheDatabase() { // Put the app into a state where it no longer uses the database. }
The new client (the one attempting the upgrade) can use the blocked
event to
detect if other clients are preventing the upgrade from happening. The blocked
event fires if other clients still hold a connection to the database after their
versionchange
events have fired.
const request= indexedDB. open( "library" , 4 ); // Request version 4. let blockedTimeout; request. onblocked= function () { // Give the other clients time to save data asynchronously. blockedTimeout= setTimeout( function () { displayMessage( "Upgrade blocked - Please close other tabs displaying this site." ); }, 1000 ); }; request. onupgradeneeded= function ( event) { clearTimeout( blockedTimeout); hideMessage(); // ... }; function hideMessage() { // Hide a previously displayed message. }
The user will only see the above message if another client fails to disconnect from the database. Ideally the user will never see this.
2. Constructs
A name is a string equivalent to a DOMString
;
that is, an arbitrary sequence of 16-bit code units of any length,
including the empty string. Names are always compared as
opaque sequences of 16-bit code units.
If an implementation uses a storage mechanism which does not support arbitrary strings, the implementation can use an escaping mechanism or something similar to map the provided name to a string that it can store.
To create a sorted name list from a list names, run these steps:
-
Let sorted be names sorted in ascending order with the code unit less than algorithm.
-
Return a new
DOMStringList
associated with sorted.
Details
This matches thesort()
method on an Array
of
String
. This ordering compares the 16-bit code units in each
string, producing a highly efficient, consistent, and deterministic
sort order. The resulting list will not match any particular
alphabet or lexicographical order, particularly for code points
represented by a surrogate pair.
2.1. Database
Each storage key has an associated set of databases. A database has zero or more object stores which hold the data stored in the database.
A database has a name which identifies it within a specific storage key. The name is a name, and stays constant for the lifetime of the database.
A database has a version. When a database is first created, its version is 0 (zero).
NOTE: Each database has one version at a time; a database can’t exist in multiple versions at once. The only way to change the version is using an upgrade transaction.
A database has at most one associated upgrade transaction, which is either null or an upgrade transaction, and is initially null.
2.1.1. Database connection
Script does not interact with databases directly. Instead, script has indirect access via a connection. A connection object can be used to manipulate the objects of that database. It is also the only way to obtain a transaction for that database.
The act of opening a database creates a connection. There may be multiple connections to a given database at any given time.
A connection can only access databases associated with the storage key of the global scope from which the connection is opened.
NOTE:
This is not affected by changes to the Document
’s
domain
.
A connection has a version, which is set when the connection is created. It remains constant for the lifetime of the connection unless an upgrade is aborted, in which case it is set to the previous version of the database. Once the connection is closed the version does not change.
Each connection has a close pending flag which is initially false.
When a connection is initially created it is in an opened state. The connection can be closed through several means. If the execution context where the connection was created is destroyed (for example due to the user navigating away from that page), the connection is closed. The connection can also be closed explicitly using the steps to close a database connection. When the connection is closed its close pending flag is always set to true if it hasn’t already been.
A connection may be closed by a user agent in exceptional circumstances, for example due to loss of access to the file system, a permission change, or clearing of the storage key’s storage. If this occurs the user agent must run close a database connection with the connection and with the forced flag set to true.
A connection has an object store set, which is initialized to the set of object stores in the associated database when the connection is created. The contents of the set will remain constant except when an upgrade transaction is live.
A connection’s get the parent algorithm returns null.
An event with type versionchange
will be fired at an open
connection if an attempt is made to upgrade or delete the
database. This gives the connection the opportunity to close
to allow the upgrade or delete to proceed.
An event with type close
will be fired at a connection if the connection is closed abnormally.
2.2. Object store
An object store is the primary storage mechanism for storing data in a database.
Each database has a set of object stores. The set of object stores can be changed, but only using an upgrade transaction,
i.e. in response to an upgradeneeded
event. When a
new database is created it doesn’t contain any object stores.
An object store has a list of records which hold the data stored in the object store. Each record consists of a key and a value. The list is sorted according to key in ascending order. There can never be multiple records in a given object store with the same key.
An object store has a name, which is a name. At any one time, the name is unique within the database to which it belongs.
An object store optionally has a key path. If the object store has a key path it is said to use in-line keys. Otherwise it is said to use out-of-line keys.
An object store optionally has a key generator.
An object store can derive a key for a record from one of three sources:
-
A key generator. A key generator generates a monotonically increasing numbers every time a key is needed.
-
Keys can be derived via a key path.
-
Keys can also be explicitly specified when a value is stored in the object store.
2.2.1. Object store handle
Script does not interact with object stores directly. Instead, within a transaction, script has indirect access via an object store handle.
An object store handle has an associated object store and an associated transaction. Multiple handles may be associated with the same object store in different transactions, but there must be only one object store handle associated with a particular object store within a transaction.
An object store handle has an index set, which is initialized to the set of indexes that reference the associated object store when the object store handle is created. The contents of the set will remain constant except when an upgrade transaction is live.
An object store handle has a name, which is initialized to the name of the associated object store when the object store handle is created. The name will remain constant except when an upgrade transaction is live.
2.3. Values
Each record is associated with a value. User agents must
support any serializable object. This includes simple types
such as String
primitive values and Date
objects as well as
Object
and Array
instances, File
objects, Blob
objects, ImageData
objects, and so on. Record values are
stored and retrieved by value rather than by reference; later changes
to a value have no effect on the record stored in the database.
Record values are Records output by the StructuredSerializeForStorage operation.
2.4. Keys
In order to efficiently retrieve records stored in an indexed database, each record is organized according to its key.
A key has an associated type which is one of: number, date, string, binary, or array.
A key also has an associated value, which will
be either:
an unrestricted double
if type is number or date,
a DOMString
if type is string,
a byte sequence if type is binary,
or a list of other keys if type is array.
An ECMAScript [ECMA-262] value can be converted to a key by following the steps to convert a value to a key.
-
Number
primitive values, except NaN. This includes Infinity and -Infinity. -
Date
objects, except where the [[DateValue]] internal slot is NaN. -
String
primitive values. -
ArrayBuffer
objects (or views on buffers such asUint8Array
). -
Array
objects, where every item is defined, is itself a valid key, and does not directly or indirectly contain itself. This includes empty arrays. Arrays can contain other arrays.
Attempting to convert other ECMAScript values to a key will fail.
An array key is a key with type array. The subkeys of an array key are the items of the array key’s value.
To compare two keys a and b, run these steps:
-
Let ta be the type of a.
-
Let tb be the type of b.
-
If ta does not equal tb, then run these steps:
-
If ta is array, then return 1.
-
If tb is array, then return -1.
-
If ta is binary, then return 1.
-
If tb is binary, then return -1.
-
If ta is string, then return 1.
-
If tb is string, then return -1.
-
If ta is date, then return 1.
-
Assert: tb is date.
-
Return -1.
-
-
Let va be the value of a.
-
Let vb be the value of b.
-
Switch on ta:
- number
- date
-
-
If va is greater than vb, then return 1.
-
If va is less than vb, then return -1.
-
Return 0.
-
- string
-
-
If va is code unit less than vb, then return -1.
-
If vb is code unit less than va, then return 1.
-
Return 0.
-
- number