This document defines APIs for a database of records holding simple values and hierarchical objects. Each record consists of a key and some value. Moreover, the database maintains indexes over records it stores. An application developer directly uses an API to locate records either by their key or by using an index. A query language can be layered on this API. An indexed database can be implemented using a persistent B-tree data structure.

Indexed Database API

Algorithms

Opening a database

The steps for opening a database are defined in the following steps. The algorithm in these steps takes three required arguments: an origin, which requested the database to be opened, a database name, and a database version. The algorithm also takes two optional arguments, a request which represents a request used when opening the database is done by using an asynchronous API or a upgrade callback which represents the callback used when opening the database is done by using the synchronous API.

  1. If these steps fail for any reason, return an error with the appropriate type and abort this algorithm.
  2. If there is already a database with the given name from the origin origin, then let db be that database.
  3. If db was found in previous step, wait until the following conditions are all fulfilled:
    • No already existing connections to db, have non-finished "versionchange" transaction.
    • If db has its delete pending flag set, wait until db has been deleted.
    • These steps are not run for any other connections with the same origin and name but with a higher version.

    If several connections with the same origin, name and version are opened at the same time, and that version is a higher version that the database's current version, then once any of those connections can proceed to the next step in this algorithm it will immediately start a "versionchange" transaction. This prevents the other connections from proceeding until that "versionchange" transaction is finished.

    This means that if two databases with the same name and origin, but with different versions, are being opened at the same time, the one with the highest version will attempt to be opened first. If it is able to successfully open, then the one with the lower version will receive an error.

  4. If no database with the given name from the origin origin was found, or if it was deleted during the previous step, then create a database with name name, with 0 as version, and with no object stores. Let db be the new database.
  5. If the version of db is higher than version, abort these steps and return a DOMError of type VersionError.
  6. Create a new connection to db and let connection represent it.
  7. If the version of db is lower than version, then run the steps for running a "versionchange" transaction using connection, version, request and upgrade callback.
  8. If the previous step resulted in an error, then return that error and abort these steps. If the "versionchange" transaction in the previous step was aborted, or if connection is closed, return a DOMError of type AbortError and abort these steps. In either of these cases, ensure that connection is closed by running the steps for closing a database connection before these steps are aborted.
  9. Return connection.

Transaction Creation steps

When the user agent is to create a transaction it MUST run the following steps. This algorithm takes five parameters: A connection, a mode, a list of storeNames of object stores to be included in the scope of the transaction, a timeout for the transaction starting, and a callback parameter for synchronously created transactions.

  1. If these steps are already running synchronously (a transaction was created within a transaction callback), throw a DOMException of type InvalidStateError.
  2. If storeNames is of type DOMStringList or Array leave it as is. Otherwise, interpret it as an Array with one value, and that value is the stringified version of storeNames. If any of the strings in storeNames identifies an object store which doesn't exist, throw a DOMException of type NotFoundError. If storeNames is an empty list throw a DOMException of type InvalidAccessError.
  3. If the closePending flag is set on connection the throw a DOMException of type InvalidStateError.
  4. Create a transaction using connection as connection, mode as mode, and the object stores identified in storeNames as scope.
  5. If these steps are running asynchronously, return the created transaction and queue up the remaining steps. When control is returned to the event loop, the implementation MUST set the active flag to false.
  6. Wait until the transaction can be started according to the transaction lifetime rules. If this takes longer than the specified timeout then a DOMException of type TimeoutError should be thrown.

    Because the asynchronous API always passes in a timeout of infinite, only the synchronous API will ever time out.

  7. If these steps are running synchronously, the implementation MUST synchronously call callback with a single parameter which is the transaction. If an exception is thrown and not caught within the scope of the callback, the implementation MUST abort the transaction by following the steps for aborting a transaction using the name of the exception that was thrown as the error parameter, abort this algorithm without taking any further steps, and re-throw the exception.
  8. If these steps are running synchronously, the implementation MUST commit the transaction synchronously.

Steps for committing a transaction

When taking the steps for committing a transaction the implementation MUST execute the following algorithm. This algorithm takes one parameter, the transaction to commit.

  1. All the changes made to the database by the transaction are written to the database.
  2. If an error occurs while writing the changes to the database, abort the transaction by following the steps for aborting a transaction with the transaction parameter set to transaction and the error parameter set to a value appropriate for the error, for example QuotaExceededError or UnknownError.
  3. Queue up an operation to dispatch an event at transaction. The event must use the Event interface and have its type set to "complete". The event does not bubble and is not cancelable. The propagation path for the event is transaction's connection and then transaction.

    Note that even if an exception is thrown from one of the event handlers of this event, the transaction is still committed since writing the database changes happens before the event takes places. Only after the transaction has been successfully written is the "complete" event fired.

Steps for aborting a transaction

When taking the steps for aborting a transaction the implementation MUST execute the following algorithm. This algorithm takes two parameters, the transaction to abort and an error name.

  1. All the changes made to the database by the transaction are reverted. For "versionchange" transactions this includes changes to the set of object stores and indexes, as well as the change to the version. Also run the steps for aborting a "versionchange" transaction which reverts changes to all IDBDatabase and IDBObjectStore instances.
  2. Unless error was set to null, create a DOMError object and set its name to error. Set transaction's error property to this newly created DOMError.
  3. If the transaction's request list contain any requests whose done flag is still false, abort the steps for asynchronously executing a request for each such request and queue a task to perform the following steps:
    1. Set the done flag on the request to true, set result of the request to undefined and set the request's error attribute to a DOMError with a type of AbortError.
    2. Dispatch an event at request. The event must use the Event interface and have its type set to "error". The event bubbles and is cancelable. The propagation path for the event is transaction's connection, then transaction and finally the request. There is no default action for the event.

    This does not always result in any error events being fired. For example if a transaction is aborted due to an error while committing the transaction, or if it was the last remaining request that failed.

  4. Queue up an operation to dispatch an event at transaction. The event must use the Event interface and have its type set to "abort". The event does bubble but is not cancelable. The propagation path for the event is transaction's connection and then transaction.

Steps for asynchronously executing a request

When taking the steps for asynchronously executing a request the implementation MUST run the following algorithm. The algorithm takes a source object and an operation to perform on a database.

These steps can be aborted at any point if the transaction the created request belongs to is aborted using the steps for aborting a transaction

  1. Set transaction to the transaction associated with source.
  2. If transaction is not active throw a DOMException of type TransactionInactiveError.
  3. Create an IDBRequest object and set request to this object. Set request's source to source and add request to the end of the list of requests in transaction. Return this object and queue up the execution of the remaining steps in this algorithm.

    Cursors override this step to reuse an existing IDBRequest. However they still put the IDBRequest at the end of the list of requests in transaction.

  4. Wait until all previously added requests in transaction have their done flag set to true.
  5. Perform operation.
  6. If performing operation succeeded then set the done flag on the request to true, set result of the request to the result of the request and set the error attribute of the request to undefined. Finally fire a success event at request.
  7. If performing operation failed then revert all changes made by operation, set the done flag on the request to true, set result of the request to undefined and set the error attribute on the request to the a DOMError with the same error type of the operation that failed. Finally fire an error event at request.
    This only reverts the changes done by this request, not any other changes made by the transaction.

Steps for synchronously executing a request

When taking the steps for synchronously executing a request the implementation MUST run the following algorithm. The algorithm takes a source object and an operation to perform on a database.

  1. If the transaction associated with source is not active throw a DOMException of type TransactionInactiveError.
  2. Perform operation.
  3. If performing operation succeeded then return the result of the operation.
  4. If performing operation failed, then throw a DOMException with the type of error from the operation.

Steps for extracting a key from a value using a key path

When taking the steps for extracting a key from a value using a key path, the implementation MUST run the following algorithm. The algorithm takes a key path named keyPath and a value named value and in some cases returns a key which may or may not be a valid key.

  1. If keyPath is an Array, then let result be newly constructed empty Array. For each item in the keyPath Array, perform the following substeps:
    1. Evaluate the steps for extracting a key from a value using a key path using the item from the keyPath array as keyPath and value as value.
    2. If the result of the previous step was not a valid key, then abort the overall algorithm and no value is returned.
    3. Add the result of the first sub-step to end of the result array.
    Return result as result of this algorithm and perform no additional steps.

    This will only ever "recurse" one level since keyPath arrays can't ever be nested.

  2. If keyPath is the empty string, return value and skip the remaining steps.
  3. Let remainingKeypath be keyPath and object be value.
  4. If remainingKeypath has a period in it, assign remainingKeypath to be everything after the first period and assign attribute to be everything before that first period. Otherwise, assign attribute to be remainingKeypath and assign remainingKeypath to be null.
  5. If object does not have an attribute named attribute, then skip the rest of these steps and no value is returned.
  6. Assign object to be the value of the attribute named attribute on object.
  7. If remainingKeypath is not null, go to step 3.
  8. Return object.

"versionchange" transaction steps

The steps for running a "versionchange" transaction are as follows. This algorithm takes two required parameters: a connection object which is used to update the database, and a new version to be set for the database. The algorithm also takes two optional arguments: a request which represents a request used when the asynchronous API is used, or a upgrade callback which represents the callback used when the synchronous API is used.

  1. Let openDatabases be the set of all IDBDatabase and IDBDatabaseSync objects, except connection, connected to the same database as connection.
  2. Fire a versionchange event at each object in openDatabases that is open. The event MUST NOT be fired on objects which has the closePending flag set. The event MUST use the IDBVersionChangeEvent interface and have the oldVersion property set to db's version and have the newVersion property set to version. This event MUST NOT bubble or be cancelable. The propagation path for the event is just the IDBDatabase object itself.

    Firing this event might cause one or more of the other objects in openDatabases to be closed, in which case the versionchange event MUST NOT be fired at those objects if that hasn't yet been done.

  3. If running asynchronously and any of the connections in openDatabases are still not closed, queue up a blocked event for the request. The event MUST use the IDBVersionChangeEvent interface and have the oldVersion property set to db's version and have the newVersion property set to version. This event MUST NOT bubble or be cancelable. The propagation path for the event is just request.

  4. Wait until either all objects in openDatabases are closed and all of their transactions are finished.

    If .close() is called immediately but a transaction associated with the connection keeps running for a "long time", should we also fire a blocked event?

    If, while we're waiting here, someone calls open with a version number higher than version, we should probably let that upgrade run first and bail here if it was successful

  5. Create a new transaction with mode set to "versionchange" and connection used as connection. The scope of the transaction includes every object store in connection. Set its active flag to false. Let transaction represent this transaction.
  6. Start transaction. Note that until this transaction is finished, no other connections can be opened to the same database.
  7. Let old version be database's version.
  8. Set the version of database to version. This change is considered part of the transaction, and so if the transaction is aborted, this change is reverted.
  9. If running asynchronously, schedule a task to run the following steps:
    1. Set the result property of request to connection.
    2. Set the transaction property of request to transaction.
    3. Fire a upgradeneeded event targeted at request. The event MUST use the IDBVersionChangeEvent interface and have the oldVersion property set to old version and have the newVersion property set to version. The readyState on the request is set to "done".
    4. If an exception was propagated out from any event handler while dispatching the event in the previous step, abort the transaction by following the steps for aborting a transaction with the error property set to AbortError.
    5. If for any reason the "versionchange" transaction is aborted the IDBDatabase instance which represents connection will remain unchanged. I.e., it's name, version, and objectStoreNames properties will remain the value they were before the transaction was aborted. The default attributes for the IDBDatabase are:
      Attribute Value
      name the name provided to IDBFactory.open
      version 0 (zero)
      objectStoresNames null
  10. If running synchronously, call upgrade callback and pass it transaction as the first argument and old version the second argument.
  11. Execute the transaction.
  12. When the transaction is finished, if these steps are run asynchronously, immediately set request's transaction property to null. This MUST be done in the same task as the task firing the complete or abort event, but after those events has been fired.

Steps for aborting a "versionchange" transaction

The Steps for aborting a "versionchange" transaction are as follows.

  1. IDBDatabase.name is not modified at all. Even if the transaction is used to create a new database, and thus there is no on-disk database, IDBDatabase.name remains as it was.
  2. Revert IDBDatabase.version to the version the on-disk database had before the transaction was started. If the "versionchange" transaction was started because the database was newly created, revert IDBDatabase.version to version 0; if the transaction was started in response to a version upgrade, revert to the version it had before the transaction was started. Note that the only time that the IDBDatabase.version property ever changes value is during a "versionchange" transaction.
  3. Revert IDBDatabase.objectStoreNames to the list of names that it had before the transaction was started. If the "versionchange" transaction was started because the database was newly created, revert it to an empty list. If the "versionchange" transaction was started in response to a version upgrade, revert to the list of object store names it had before the transaction was started.
  4. Revert IDBObjectStore.indexNames (for each object store) to the list of names that IDBObjectStore.indexNames had before the transaction was started. For any object store that was created by the transaction, revert the list to an empty list. For any object store that existed before the transaction was started, revert to the list of index names it had before the transaction was started. For any object store that was deleted during the transaction, revert the list of names to the list it had before the transaction was started, potentially a non-empty list.

    Although you cannot access object stores by using the IDBTransaction.objectStore function after a transaction is aborted, the page may still have references to object store instances. In that case IDBObjectStore.indexNames can still be accessed.

Database closing steps

The steps for closing a database connection are as follows. These steps take one argument, a connection object.

  1. Set the internal closePending flag of connection to true.
  2. Wait for all transactions created using connection to complete. Once they are complete, connection is closed.

Once the closePending flag has ben set to true no new transactions can be created using connection. All functions that create transactions first check the closePending flag first and throw an exception if it is true.

Once the connection is closed, this can unblock the steps for running a "versionchange" transaction, and the steps for deleting a database, which both wait for connections to a given database to be closed before continuing.

Database deletion steps

The steps for deleting a database are as follows. The algorithm in these steps takes three arguments: the origin that requested the database to be deleted, a database name, and an optional request representing a request used when deleting the database from an asynchronous API.

  1. If there is already a database with the given name from the origin origin, then let db be that database.
  2. If no database was found, then these steps are considered successful. Abort these steps.
  3. Set db's delete pending flag to true.
  4. Let openDatabases be the set of all IDBDatabase and IDBDatabaseSync objects connected to db.
  5. Fire a versionchange event at each object in openDatabases that is open. The event MUST NOT be fired on objects which has the closePending flag set. The event MUST use the IDBVersionChangeEvent interface and have the oldVersion property set to db's version and have the newVersion property set to null. This event MUST NOT bubble or be cancelable.

    Firing this event might cause one or more of the other objects in openDatabases to be closed, in which case the versionchange event MUST NOT be fired at those objects if that hasn't yet been done.

  6. If any of the connections in openDatabases are still not closed, and request was provided, fire a blocked event at request. The event MUST use the IDBVersionChangeEvent interface and have the oldVersion property set to db's version and have the newVersion property set to null. This event MUST NOT bubble or be cancelable.

  7. Wait until all objects in openDatabases are closed and all of their transactions are finished.

    Should we allow blocked to be fired here too, if waiting takes "too long"?

  8. Delete db.

Fire a success event

To fire a success event at a request, the implementation MUST run the following steps:

  1. Set transaction to the transaction associated with the source.
  2. Set the active flag of transaction to true.
  3. Dispatch an event at request. The event must use the Event interface and have its type set to "success". The event does not bubble and is not cancelable. The propagation path for the event is the transaction's connection, then transaction and finally request.
  4. Set the active flag of transaction to false.
  5. If an exception was propagated out from any event handler while dispatching the event in step 3, abort the transaction by following the steps for aborting a transaction using transaction as transaction parameter, and AbortError as error.

Fire an error event

To fire an error event at a request, the implementation MUST run the following steps:

  1. Set transaction to the transaction associated with the source.
  2. Set the active flag of transaction to true.
  3. Dispatch an event at request. The event must use the Event interface and have its type set to "error". The event bubbles and is cancelable. The propagation path for the event is the transaction's connection, then transaction and finally request. The event's default action is to abort the transaction by running the steps for aborting a transaction using transaction as transaction and the name of request's error property as error. However the default action is not taken if any of the event handlers threw an exception.
  4. Set the active flag of transaction to false.
  5. If an exception was propagated out from any event handler while dispatching the event in step 3, abort the transaction by following the steps for aborting a transaction using transaction as transaction parameter, and AbortError as error. This is done even if the error event is not canceled.

    This means that if an error event is fired and any of the event handlers throw an exception, the error property on the transaction is set to an AbortError rather than whatever DOMError the error property on the request was set to. Even if preventDefault is never called.

Steps to assign a key to a value using a key path

The steps to assign a key to a value using a key path are as follows:

  1. Let remainingKeypath be keyPath and object be value.
  2. If object is not an Object object or an Array object (see structured clone algorithm [[!HTML5]]), then throw a DOMException of type DataError.
  3. If remainingKeypath has a period in it, assign remainingKeypath to be everything after the first period, and assign attribute to be everything before that first period. Otherwise, go to step 7.
  4. If object does not have an attribute named attribute, then create the attribute and assign it an empty object.
  5. Assign object to be the value of the attribute named attribute on object.
  6. Go to step 2.
  7. The steps leading here ensure that remainingKeyPath is a single attribute name (that is, a string without periods) by this step. The steps also ensure that object is an Object or an Array, and not a Date, RegExp, Blob, or other nonsupported type.
  8. Let attribute be remainingKeyPath.
  9. Set an attribute named attribute on object with the value key.

The intent is that these steps are only executed if evaluating the key path did not yield a value. In other words, before you run these steps, first evaluate the key path against value, and only if that does not yield a value (where 'undefined' does count as a value) do you generate a key and use these steps to modify value to contain the generated key.

The key path used here is always a DOMString and never an Array since it is not possible to create a object store whose multiEntry flag is true and whose key path is an Array.

Database operations

This section describes various operations done on the data in object stores and indexes in a database. These operations are run by the steps for asynchronously executing a request and the steps for synchronously executing a request.

Object Store Storage Operation

The steps for storing a record into an object store are as follows. The algorithm run by these steps takes four parameters: an object store store, a value, an optional key, and a no-overwrite flag.

  1. If store does use in-line keys and evaluting store's key path on value does yield a value, then set key to that result.
  2. If store uses a key generator and key is undefined, set key to the next generated key. If store also uses in-line keys, then set the property in value pointed to by store's key path to the new value for key, as shown in the steps to assign a key to a value using a key path.
  3. If store uses a key generator, this key generator was not used to generate a value for key in the previous step, key is defined to a long or a float and this number is larger than, or equal to, the next key that store's key generator would generate, change store's key generator such that the next key it generates is the lowest integer larger than key.
  4. If the no-overwrite flag was passed to these steps and is set, and a record already exists in store with its key equal to key, then this operation failed with a ConstraintError. Abort this algorithm without taking any further steps.
  5. If a record already exists in store with its key equal to key, then remove the record from store using the steps for deleting records from an object store.
  6. Store a record in store containing key as its key and object as its value. The record is stored in the object store's list such that the list is sorted according key of the records in ascending order.
  7. If there are any indexes which reference store, perform the following sub steps on each such index.
    1. Set index to the index.
    2. Evaluate index's key path on value. If this does not yield a value, take no further actions for this index. Otherwise set the result to index key.
    3. If index's multiEntry flag is false or if index key is not an Array, and if index key is not a valid key, take no further actions for this index.
    4. If index's multiEntry flag is true, and index key is an Array, remove any elements from index key that are not valid keys and remove any duplicate elements from index key such that only one instance of the duplicate value remains.

      For example, the following value of index key [10, 20, null, 30, 20] is converted to [10, 20, 30].

      After this step index key is or contains only valid keys.

    5. If index's multiEntry flag is false, or if index key is not an Array, and if index already contains a record with key equal to index key, and index has it's unique flag set to true, then this operation failed with a ConstraintError. Abort this algorithm without taking any further steps.
    6. If index's multiEntry flag is true and index key is an Array, and if index already contains a record with key equal to any of the values in index key, and index has it's unique flag set to true, then this operation failed with a ConstraintError. Abort this algorithm without taking any further steps.
    7. If index's multiEntry flag is false, or if index key is not an Array, then store a record in index containig index key as its key and key as its value. The record is stored in index's list of records such that the list is sorted primarily on the records keys, and secondarily on the records values, in ascending order.
    8. If index's multiEntry flag is true and index key is an Array, then for each item in index key store a record in index containig the items value as its key and key as its value. The records are stored in index's list of records such that the list is sorted primarily on the records keys, and secondarily on the records values, in ascending order.

      Note that it is legal for the Array to have length 0, in this case no records are added to the index.

      If any of the items in the Array are themselves an Array, then the inner Array is used as a key for that entry. In other words, Arrays are not recursively "unpacked" to produce multiple rows. Only the outer-most Array is.

  8. The result of this algorithm is key.

Object Store Retrieval Operation

The steps for retrieving a value from an object store are as follows. These steps MUST be run with two parameters - the record key and the object store.

  1. Let key be the key and store be the object store passed to these steps.
  2. If key is not a key range then retreive the record with key key from store. If key is a key range, then retreive the first record from store whose key is in key.
  3. If no record was found, the result of this algorithm is undefined.
  4. The result of this algorithm is a new structured clone of the value in the found record.

Index Referenced Value Retrieval Operation

The steps for retrieving a referenced value from an index are as follows. These steps MUST be run with two parameters - the record key and the index.

  1. Let key be the key and index be the index passed to these steps.
  2. If key is not a key range then find the first record with key key from index. If key is a key range, then find the first record from index whose key is in key.
  3. If no record was found, the result of this algorithm is undefined.
  4. Otherwise, the result of the operation is a structured clone of the referenced value of the found record.

Index Value Retrieval Operation

The steps for retrieving a value from an index are as follows. These steps MUST be run with two parameters - the record key and the index.

  1. Let key be the key and index be the index passed to these steps.
  2. If key is not a key range then find the first record with key key from index. If key is a key range, then find the first record from index whose key is in key.
  3. If no record was found, the result of this algorithm is undefined.
  4. If a record was found, the result of this algorithm is the value of the found record.

Object Store Deletion Operation

The steps for deleting records from an object store are as follows. The algorithm run by these steps takes two parameters: an object store store and a key.

  1. If the key parameter is a key range then let range be that key range. Otherwise, let range be a key range which containing only key.
  2. Remove all records, if any, from store with key in range.
  3. In all indexes which reference store, remove all records whose value is in range, if any such records exist.
  4. The result of this algorithm is undefined.

Object Store Clear Operation

The steps for clearing an object store are as follows. The algorithm run by these steps takes one parameter: an object store store.

  1. Remove all records from store.
  2. In all indexes which reference store, remove all records.
  3. The result of this algorithm is undefined.

Cursor Iteration Operation

The steps for iterating a cursor are as follows. The algorithm run by these steps takes two parameters: a cursor and optional key to iterate to.

  1. Let source be cursor's source, let records be list of records in source, let direction be cursor's direction, let position be cursor's position, let object store position be cursor's object store position and let range be cursor's range.

    source is always an object store or an index.

    records is always sorted in ascending key order. In the case of source being an index, records is secondarily sorted in ascending value order.

  2. If direction is "next", let found record be the first record in records which satisfy all of the following requirements:

    If direction is "nextunique", let found record be the first record in records which satisfy all of the following requirements:

    • If key is defined, the record's key is greater than or equal to key.
    • If position is defined, the record's key is greater than position.
    • If range is defined, the record's key is in range.

    If direction is "prev", let found record be the last record in records which satisfy all of the following requirements:

    • If key is defined, the record's key is less than or equal to key.
    • If position is defined, and source is an object store, the record's key is less than position.
    • If position is defined, and source is an index, the record's key is equal to position and the record's value is less than object store position or the record's key is less than position.
    • If range is defined, the record's key is in range.

    If direction is "prevunique", let temp record be the last record in records which satisfy all of the following requirements:

    • If key is defined, the record's key is less than or equal to key.
    • If position is defined, the record's key is less than position.
    • If range is defined, the record's key is in range.

    If temp record is defined, let found record be the first record in records whose key is equal to temp record's key.

  3. If found record is not defined, set cursor's key and primary key to undefined. If cursor implements IDBCursorWithValue or IDBCursorWithValueSync, then set cursor's value to undefined. The result of this algorithm is null. Abort these steps.
  4. Set cursor's position to found record's key. If source is an index, set cursor's object store position to found record's value.

  5. Set cursor's key to found record's key.

    If cursor implements IDBCursorWithValue or IDBCursorWithValueSync, then set cursor's value to a structured clone of found record referenced value.

  6. Set cursor's got value flag to true.

    Once data has been successfully read, schedule a task which when run will set the cursor's value and fire a success event.

  7. The result of the algorithm is cursor.