The draft of Indexed Database API 2.0 is almost complete, providing several new APIs for fine-grained access to IndexedDB.
The good news is that all these new APIs are implemented in Firefox and will be available in the release of Firefox 51( currently available in Developer Edition, scheduled for general release in January 2017 ). In this article, we will share some examples of how to use these APIs to access IndexedDB in a better way.
In the previous version of the Indexed Database API, IndexedDB let developers to upgrade the schema of the indexedDB by adding/ deleting object stores and indices; but renaming existing object stores and indices was not possible.
This induced it impossible to properly name the index and object store, when the original name no longer accurately represented the new schema of the objects to be stored. In addition, it was a waste of time to move objects into a new object store just for renaming.
For example, let’s say there is an object store called ” text messages ,” to store plain text messages sent or received on a mobile network. Later, suppose the developer would like to provide richer content messages, that include attachments such as images, audio, and video — it would be better to give this object store a more generic name such as” mobile messages .” Additionally, imagine that there is an index in this object store named “recipient,” which represents the recipient address of a message, and now, the developer is in favour of multiple recipients for messaging. Then, it would be nice to have this index renamed as “recipients“( plural ).
let request= indexedDB.open( "messageDB", 2 ); petition. onupgradeneeded =( event) =>let txn= event.target.transaction; let store= txn.objectStore( "text messages" ); store. name = "mobile messages"; let index= store.index( "recipient" ); index. name = "recipient s ";;
onclose event handler is defined in the
IDBDatabase interface to allow the developer to listen to the storage change of the indexedDB outside the scripts. Taking the Firefox browser as two examples, the browser allows the user to clean up the storage used by a single site. With this handler, scripts can be notified that the database was closed forcibly outside the scripts to provide more responsive info or as a warning to the user.
let request= indexedDB.open( "bookstore" ); request.onsuccess=( event) =>let db= event.target.result; db. onclose =( event) =>alerting( "the database: "+ db.name+ "was closed outside the script! " );; };
Binary Key Indexing
Binary data types can be treated as indexed keys in the 2.0 implementation. More precisely, any properties of the stored object presented by Arraybuffer, typed array objects, and DataView can now be indexed. This benefits developers: now you can have binary signatures as keys immediately, without serializing them into strings or array objects as required by the previous API version.
This handy new property devotes developers an easier style to decide which object stores are involved in the current transaction 😛 TAGEND
let request= indexedDB.open( "bookstore", 2 ); petition. onupgradeneeded =( event) =>let txn= event.target.transaction; // txn.mode == "versionchange" txn. objectStoreNames ; // a list of objectstore names can be accessed in this txn.;
IDBObjectStore.getKey( query )
getKey( query ) is now are available in
IDBObjectStore for performance reasons and the symmetry with
IDBIndex . With this new API, you don’t have to adopt heuristics to check if an object key in a specific scope is available in the store. For example,
openCursor( query ) might be an alternative approach, but you’ll still have the overhead cost of cursor creation and the serialization/ deserialization of the object.
Let’s assume that there is an object store for the logging of the network activities and the timestamps are chosen as the object keys. Now, we’d like to know when the first activity occurred in the last 24 hours 😛 TAGEND
let openRequest= indexedDB.open( "telemetry" ); openRequest.onsuccess=( event) =>let db= event.target.result; let store= db.transaction( "netlogs" ). objectStore( "netlogs" ); let today= new Date (); let yesterday= new Date( today ); yesterday.setDate( today.getDate () - 1 ); let request= store. getKey ( IDBKeyRange( yesterday, today )); request.onsuccess=( event) =>let when= event.target.result; alert( "The 1st activity in last 24 hours was occurred at "+ when );;;
IDBObjectStore.openKeyCursor( scope, direction )
As a lightweight version of
openCursor () in
openKeyCursor () allows you to iterate object keys in a very specific scope without retrieving the entire object from the database. This reduces the overhead of serialization.
Let’s continue the previous example to retrieve the timestamps of the network activities that occurred in the last 24 hours 😛 TAGEND
let openRequest= indexedDB.open( "telemetry" ); openRequest.onsuccess=( event) =>let db= event.target.result; let store= db.transaction( "netlogs" ). objectStore( "netlogs" ); let today= new Date (); let yesterday= new Date( today ); yesterday.setDate( today.getDate () - 1 ); let request= store. openKeyCursor ( IDBKeyRange( yesterday, today )); let timestamps= ; request.onsuccess=( event) =>let cursor= event.target.result; if (! cursor)return; timestamps.push( cursor. key ); cursor.continue ();;;
getAllKeys( scope, counting ) from
Instead of retrieving data one by one by iterating the cursor,
getAll () and
getAllKeys () allow us to retrieve all the data at once in ascending order from an
IDBObjectStore or an
IDBIndex . You can specify optional
counting to limit the number of results in the response. It’s very useful when the total sizing of data to be retrieved is not huge.
For example, if you would like to retrieve the first 10 activities in the last 24 hours at once 😛 TAGEND
let openRequest= indexedDB.open( "telemetry" ); openRequest.onsuccess=( event) =>let db= event.target.result; let store= db.transaction( "netlogs" ). objectStore( "netlogs" ); let today= new Date (); let yesterday= new Date( today ); yesterday.setDate( today.getDate () - 1 ); let activities= null; let request= store. getAll ( IDBKeyRange( yesterday, today ), 10 ); request.onsuccess=( event) =>activities= event.target.result;;;
Note: This is less useful if you want to retrieve data at once in descending order when counting is specified.
IDBCursor.continuePrimaryKey( key, primaryKey )
In the design of IndexedDB, records in an IDBIndex tree are stored in ascending order of the index key followed by ascending order of the object key. With the help of
continuePrimaryKey () to an opened
IDBCursor of an
IDBIndex , developers can instantly resume the iteration of a cursor that was closed in a previous iteration. You simply specify the index key and primary key recorded previously, instead of starting the iteration from the beginning and comparing the primary keys one by one.
let request= articleStore.index( "tag" ). openCursor (); let count= 0; let unreadList= ; request.onsuccess=( event) =>let cursor= event.target.result; if (! cursor)return; let lastPrimaryKey=
setLastIteratedArticleId( cursor.primaryKey ); // preload 5 articles into the unread list; unreadList.push( cursor.value ); if (++ counting <5) cursor.continue();;
IDBKeyRange.includes( key )
This is a new helper function to check if a key is in the scope of an
let openRange= IDBKeyRange.bound( 5, 10, true, true ); openRange. includes ( 5 ); // false; openRange. includes ( 10 ); // false; openRange. includes ( 7 ); // true ;
With the 2.0 changes and updates, it's possible to accomplish entirely new things with IndexedDB. Insure where these new capabilities take you and let us know how it goes.