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.
Each origin has an associated set of databases. A database comprises one or more object stores which hold the data stored in the database.
Every database has a name which identifies it within a specific origin. The name can be any string value, including the empty string, and stays constant for the lifetime of the database. Each database also has a current version. When a database is first created, its version is 0.
Implementations MUST support all names. If an implementation uses a storage mechanism which can't handle arbitrary database names, the implementation must use an escaping mechanism or something similar to map the provided name to a name that it can handle.
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 a "versionchange"
transaction.
Databases has a delete pending flag which is used during deletion. When a database is requested to be deleted the flag is set to true and all attempts at opening the database are stalled until the database can be deleted.
The act of opening a database creates a connection. There MAY be multiple connections to a given database at any given time. Each connection has a closePending flag which initially is set to false.
When a connection is initially created it is in opened state. The connection can be closed through several means. If the connection is GCed or execution context where the connection is 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 for closing a database connection. When the connection is closed the closePending flag is always set to true if it hasn't already been.
The IDBDatabase
and IDBDatabaseSync
interfaces represent a connection to a database.
An object store is the primary storage mechanism for storing data in a database.
Each database contain a set of object stores. The set of object stores can be changed,
but can only be changed using a "versionchange"
transactions, i.e. in response to a
upgradeneeded
event. When a new database is created it doesn't contain any object stores.
The 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.
Every object store has a name. The name is unique within the database to which it belongs. Every object store also optionally has a key generator and an optional 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.
The object store can derive the key from one of three sources:
The IDBObjectStore
and IDBObjectStoreSync
interfaces represent an object store. Note however that multiple instances of those
interfaces representing the same object store can exist.
In order to efficiently retrieve records stored in an indexed database,
each record is organized according to its key. A value is said to be a valid key
if it is one of the following types:
Array
JavaScript objects [[!ECMA-262]], DOMString
[[!WEBIDL]],
Date
[[!ECMA-262]] or float
[[!WEBIDL]].
However Array
s are only valid keys if every item in the array is defined and is
a valid key (i.e. sparse arrays can not be valid keys) and if the Array
doesn't directly or
indirectly contain itself.
Any non-numeric properties are ignored, and thus does not affect whether the Array
is a valid key.
Additionally, if the value is of type float
, it is only a valid key if it is not NaN
,
and if the value is of type Date
it is only a valid key if its [[PrimitiveValue]]
internal property, as defined by [[!ECMA-262]], is not NaN
.
Conforming user agents MUST support all valid keys as keys.
Infinite float
values are valid keys. As are empty Array
s.
For purposes of comparison, all Array
s are greater than all DOMString
,
Date
and float
values; all DOMString
values are greater than all
Date
and float
values; and all Date
values are greater than all
float
values. Values of type float
are compared to other float
values
numerically. Values of type Date
are compared to other Date
values chronologically.
Values of type DOMString
are compared to other values of type DOMString
by using the algorithm defined by step 4 of section 11.8.5, The Abstract Relational Comparison Algorithm,
of the ECMAScript Language Specification [[!ECMA-262]].
Values of type Array
are compared to other values of type Array
as follows:
Array
value and B be the second Array
value.
Note that Array
s that contain other Array
s are allowed as valid keys.
In this case the algorithm above runs recursively when comparing the individual values in the arrays.
As a result of the above rules, negative infinity is the lowest possible value for a key. There is no highest possible key value. This is because an array of any candidate highest key followed by another valid key is even higher.
The terms greater than, less than and equal to is defined in the terms of the above comparisons.
The following examples illustrate the different behaviors when trying to use in-line keys and key generators to save an object to an object store.
If the following conditions are true:
Then the value provided by the key generator is used to populate the key value.
In the example below the key path for the object store is "foo.bar"
.
The actual object has no value for the bar
property, { foo: {} }
.
When the object is saved in the object store the bar
property is assigned a value of 4
because that is the next key generated by the object store.
If the following conditions are true:
Then the value associated with the key path property is used.
The auto-generated key is not used.
In the example below the keypath for the object store is "foo.bar"
.
The actual object has a value of 10 for the bar
property, { foo: { bar: 10} }
.
When the object is saved in the object store the bar
property keeps its value of 10, because that is the key value.
The following example illustrates the scenario when the specified in-line key is defined through a key path but there is no property matching it.
The value provided by the key generator is then used to populate the key value and the system is responsible
for creating as many properties as it requires to suffice the property dependencies on the hierarchy chain.
In the example below the key path for the object store is "foo.bar.baz"
.
The actual object has no value for the foo
property, { zip: {} }
.
When the object is saved in the object store the foo
, bar
, and baz
properties
are created each as a child of the other until a value for foo.bar.baz
can be assigned.
The value for foo.bar.baz
is the next key generated by the object store.
Attempting to store a property on a primitive value will fail and throw an error.
In the first example below the key path for the object store is "foo"
.
The actual object is a primitive with the value, 4.
Trying to define a property on that primitive value fails.
The same is true for arrays. Properties are not allowed on an array.
In the second example below, the actual object is an array, [10].
Trying to define a property on the array fails.
Each record is associated with a value. Conforming user agents MUST support
any value supported by the structured clone algorithm
[[!HTML5]]. This includes simple types such as DOMString
and Date
as well as Object
and Array
instances.
A key path is a DOMString
or Array
that defines how to extract a key
from a value. A valid key path is one of:
DOMString
.DOMString
containing a JavaScript identifier [[!ECMA-262]].DOMString
containing multiple Javascript identifiers separated by
periods (ASCII character code 46) [[!ECMA-262]].Array
containing only DOMString
s conforming to
the above requirements.Note that spaces are not allowed within a key path. To evaluate a key path, run the steps for extracting a key from a value using a key path.
Key path values can only be accessed from properties explicitly copied by the structured clone algorithm, as well as the following properties:
Blob.size
Blob.type
File.name
File.lastModifiedDate
Array.length
String.length
It is sometimes useful to retrieve records in an object store through other means than their key. An index allows looking up records in an object store using properties of the values in the object stores records.
An index is a specialized persistent key-value storage and has a referenced object store. The index has a list of records which hold the data stored in the index. The records in an index are automatically populated whenever records in the referenced object store are inserted, updated or deleted. There can be several indexes referencing the same object store, in which changes to the object store cause all such indexes to get updated.
The values in the index's records are always values of keys in the index's referenced object store. The keys are derived from the referenced object store's values using a key path. If a given record with key X in the object store referenced by the index has the value A, and evaluating the index's key path on A yields the result Y, then the index will contain a record with key Y and value X.
Records in an index are said to have a referenced value. This is the value of the record in the index's referenced object store which has a key equal to the index's record's value. So in the example above, the record in the index whose key is Y and value is X has a referenced value of A.
Each record in an index reference one and only one record in the index's referenced object store. However there can be multiple records in an index which reference the same record in the object store. And there can also be no records in an index which reference a given record in an object store.
The records in an index are always sorted according to the records key. However unlike object stores, a given index can contain multiple records with the same key. Such records are additionally sorted according to the records value.
An index contains a unique flag. When this flag is set to true, the index enforces that no two records in the index has the same key. If a record in the index's referenced object store is attempted to be inserted or modified such that evaluating the index's key path on the records new value yields a result which already exists in the index, then the attempted modification to the object store fails.
An index also contains a multiEntry flag. This flag affects how the index behaves when the result of evaluating
the index's key path yields an Array
. If the multiEntry flag is false, then
a single record whose key is an Array
is added to the index. If the multiEntry flag is
true, then the one record is added to the index for each item in the Array
. The key for each
record is the value of respective item in the Array
.
The IDBIndex
and IDBIndexSync
interfaces
provide access to the metadata of an index. Note however that multiple instances of those
interfaces representing the same index can exist.
A transaction is used to interact with the data in a database. Whenever data is read or written to the database it is done by using a transaction.
All transactions are created through a connection, which is the transaction's connection. The transaction has a mode that determines which types of interactions can be performed upon that transaction. The mode is set when the transaction is created and remains fixed for the life of the transaction. The transaction also has a scope that determines the object stores with which the transaction may interact. Transactions have an active flag, which determines if new requests can be made against the transaction. Finally, transactions also contain a request list of requests which have been made against the transaction.
Each transaction has a fixed scope, determined when the transaction is created. A transaction's scope remains fixed for the lifetime of that transaction.
Transactions offer some protection from application and system failures. A transaction may be used to store multiple data records or to conditionally modify certain data records. A transaction represents an atomic and durable set of data access and data mutation operations.
Transactions are expected to be short lived. This is encouraged by the automatic committing functionality described below. Authors can still cause transactions to run for a long time; however, this usage pattern is not generally recommended as it can lead to a bad user experience.
The lifetime of a transaction is as follows:
DOMException
of type TransactionInactiveError
.
QuotaExceededError
should
be used as error, and if an IO error happened, UnknownError
should be used as
error.
Transactions are opened in one of three modes. The mode determines how concurrent access to object stores in the transaction are isolated.
"readonly"
. This mode means that the transaction is only allowed
to read data. No modifications can be done by this type of transaction. This has the advantage
that several "readonly"
transactions can run at the same time even if their scopes
are overlapping, i.e. if they are using the same object stores. This type of transaction can
be created any time once a database has been opened using the IDBDatabase.transaction() or
IDBDatabaseSync.transaction() functions."readwrite"
. The type of transaction is allowed to read, modify and
delete data from existing object stores. However object stores and indexes can't be added or removed.
Multiple "readwrite"
transactions can't run at the same time if their scopes are overlapping
since that would mean that they can modify each other's data in the middle of the transaction.
This type of transaction can be created any time once a database has been opened using the
IDBDatabase.transaction() function."versionchange"
. This is similar to a "readwrite"
transaction,
however it can additionally create and remove object stores and indexes. It's the only type of
transaction that can do so. This type of transaction can't be manually created, but instead is created
automatically when a upgradeneeded
event is fired.The this is defined in more detail below.
Any number of transactions opened in "readonly"
mode are allowed to run concurrently,
even if the transaction's scope overlap and include the same object stores.
As long as a "readonly"
transaction is running, the data that the implementation returns
through requests created with that transaction MUST remain
constant. That is, two requests to read the same piece of data MUST yield the same result
both for the case when data is found and the result is that data, and for the case when data
is not found and a lack of data is indicated.
There are a number of ways that an implementation ensures this. The implementation can prevent any
"readwrite"
transaction, whose scope overlaps the scope of the "readonly"
transaction, from
starting until the "readonly"
transaction finishes. Or the implementation can allow the "readonly"
transaction to see a snapshot of the contents of the object stores which is taken when
the "readonly"
transaction started.
Similarly, implementations MUST ensure that a "readwrite"
transaction is only affected by
changes to object stores that are made using the transaction itself. For example, the
implementation MUST ensure that another transaction does not modify the contents of
object stores in the "readwrite"
transaction's scope. The implementation
MUST also ensure that if the "readwrite"
transaction completes successfully, the
changes written to object stores using the transaction can be committed to the
database without merge conflicts. An implementation MUST NOT abort a transaction
due to merge conflicts.
If multiple "readwrite"
transactions are attempting to access the same object store (i.e.
if they have overlapping scope), the transaction that was
created first MUST be the transaction which gets access to the object store first. Due to
the requirements in the previous paragraph, this also means that it is the only transaction
which has access to the object store until the transaction is finished.
Generally speaking, the above requirements mean that "readwrite"
transactions which have overlapping
scopes always run in the order they were created, and never run
in parallel.
A "versionchange"
transaction never run concurrently with other transactions.
When a database is opened with a version number higher than the current version, a new
"versionchange"
transaction is automatically created and made available through the
"upgradeneeded"
event. The "upgradeneeded"
event isn't fired, and thus the
"versionchange"
transaction isn't started, until all other connections to the same
database are closed. This ensures that all other transactions are finished.
As long as a "versionchange"
transaction
is running, attempts to open more connections to the same database are delayed,
and any attempts to use the same connection to start additional transactions will result in
an exception being thrown. Thus "versionchange"
transactions not only ensure that
no other transactions are running concurrently, but also ensure that no other transactions are queued
against the same database as long as the transaction is running.
"versionchange"
transaction is automatically created
when a database version number is provided that is greater than the current database version.
This transaction will be active inside the onupgradeneeded
event handler,
allowing the creation of new object stores and indexes.
User agents MUST ensure a reasonable level of fairness across transactions to prevent
starvation. For example, if multiple "readonly"
transactions are started one after another
the implementation MUST NOT indefinitely prevent a pending "readwrite"
transaction from starting.
Each transaction object implements either the IDBTransaction
or the
IDBTransactionSync
interface.
Each reading and writing operation on a database is done using a request. Every request represents one read or write operation. Requests have a done flag which initially is false, and a source object. Every request also has a result and an error attribute, neither of which are accessible until the done flag is set to true.
Finally, requests have a request transaction. When a request is created, it is always placed against a transaction using either the steps for asynchronously executing a request or the steps for synchronously executing a request. The steps set the request transaction to be that transaction. The steps do not set the request transaction to be that request for the request returned from an IDBFactory.open call however. That function create requests which have a null request transaction.
Records can be retrieved from object stores and indexes using either keys or key ranges. A key range is a continuous interval over some data type used for keys.
A key range MAY be lower-bounded or upper-bounded (there is a value that is, respectively, smaller than or larger than all its elements). A key range is said to be bounded if it is both lower-bounded and upper-bounded. If a key range is neither lower-bounded nor upper-bounded it is said to be unbounded. A key range MAY be open (the key range does not include its endpoints) or closed (the key range includes its endpoints). A key range MAY consist of a single value.
The IDBKeyRange
interface defines a
key range.
undefined
and
and upperOpen set to true.
undefined
, lowerOpen set to true,
upper set to upper and
and upperOpen set to open.
A key is in a key range if both the following conditions are fulfilled:
Cursors are a transient mechanism used to iterate over multiple records in a database. Storage operations are performed on the underlying index or an object store.
A cursor comprises a range of records in either an index or an object store. The cursor has a source that indicates which index or object store is associated with the records over which the cursor is iterating. A cursor maintains a position over this series, which moves in a direction that is in either monotonically increasing or decreasing order of the record keys. Cursors also have a key and a value which represent the key and the value of the last iterated record. Cursors finally have a got value flag. When this flag is false, the cursor is either in the process of loading the next value or it has reached the end of its range, when it is true, it indicates that the cursor is currently holding a value and that it is ready to iterate to the next one.
There are four possible values for a cursor's direction. The direction of a cursor determines if the cursor initial position is at the start of its source or at its end. It also determines in which direction the cursor moves when iterated, and if it skips duplicated values when iterating indexes. The allowed values for a cursor's direction is as follows:
"next"
. This direction causes the cursor to be opened at the start of the
source. When iterated, the cursor should yield all records, including
duplicates, in monotonically increasing order of keys."nextunique"
. This direction causes the cursor to be opened at the start of the
source. When iterated, the cursor should not yield records with
the same key, but otherwise yield all records, in monotonically increasing order of keys. For every key
with duplicate values, only the first record is yielded. When the source is
an object store or a unique index, this direction has the exact same behavior as "next"
.
"prev"
. This direction causes the cursor to be opened at the end of the
source. When iterated, the cursor should yield all records, including
duplicates, in monotonically decreasing order of keys."prevunique"
. This direction causes the cursor to be opened at the end of the
source. When iterated, the cursor should not yield records with
the same key, but otherwise yield all records, in monotonically decreasing order of keys. For every key
with duplicate values, only the first record is yielded. When the source is
an object store or a unique index, this direction has the exact same behavior as "prev"
.If the source of a cursor is an object store, the effective object store of the cursor is that object store and the effective key of the cursor is the cursor's position. If the source of a cursor is an index, the effective object store of the cursor is that index's referenced object store and the effective key is the cursor's object store position.
It is possible for the list of records which the cursor is iterating over to change before the full range of the cursor has been iterated. In order to handle this, cursors maintain their position not as an index, but rather as a key of the previously returned record. For a forward iterating cursor, the next time the cursor is asked to iterate to the next record it returns the record with the lowest key greater than the one previously returned. For a backwards iterating cursor, the situation is opposite and it returns the record with the highest key less than the one previously returned.
For cursors iterating indexes the situation is a little bit more complicated since multiple records can have the same key and are therefore also sorted by value. When iterating indexes the cursor also has an object store position, which indicates the value of the previously found record in the index. Both position and the object store position are used when finding the next appropriate record.
Cursor objects implement the IDBCursor
or the IDBCursorSync
interfaces. There is only ever one
IDBCursor
or IDBCursorSync
instance representing
a given cursor. However there is no limit on how many cursors can be used at the
same time.
Each of the exceptions defined in the IndexedDB spec is a DOMException
with a specific type. [[!DOM4]]
Existing DOM Level 4 exceptions will set their code to a legacy value; however, the new indexedDB type exceptions will have a code value of 0.
The message value is optional.
IndexedDB uses the following new DOMException
types with their various messages.
All of these new types will have a code value of 0
zero.
Type | Message (Optional) |
---|---|
UnknownError |
The operation failed for reasons unrelated to the database itself and not covered by any other errors. |
ConstraintError |
A mutation operation in the transaction failed because a constraint was not satisfied. For example, an object such as an object store or index already exists and a request attempted to create a new one. |
DataError |
Data provided to an operation does not meet requirements. |
TransactionInactiveError |
A request was placed against a transaction which is currently not active, or which is finished. |
ReadOnlyError |
The mutating operation was attempted in a "readonly" transaction. |
VersionError |
An attempt was made to open a database using a lower version than the existing version. |
IndexedDB reuses the following existing DOMException types from [[!DOM4]]. These types will continue to return the codes and names as specified in DOM4; however, they will have the following messages when thrown from an IndexedDB API:
Type | Message (Optional) |
---|---|
NotFoundError |
The operation failed because the requested database object could not be found. For example, an object store did not exist but was being opened. |
InvalidStateError |
An operation was called on an object on which it is not allowed or at a time when it is not allowed. Also occurs if a request is made on a source object that has been deleted or removed. Use TransactionInactiveError or ReadOnlyError when possible, as they are more specific variations of InvalidStateError. |
InvalidAccessError |
An invalid operation was performed on an object. For example transaction creation attempt was made, but an empty scope was provided. |
AbortError |
A request was aborted, for example through a call to IDBTransaction.abort .
|
TimeoutError |
A lock for the transaction could not be obtained in a reasonable time. |
QuotaExceededError |
The operation failed because there was not enough remaining storage space, or the storage quota was reached and the user declined to give more space to the database. |
SyntaxError |
The keypath argument contains an invalid key path. |
DataCloneError |
The data being stored could not be cloned by the internal structured cloning algorithm. |
Options objects are dictionary objects [[!WEBIDL]] which are used to supply optional parameters to some indexedDB functions like createObjectStore and createIndex. The attributes on the object correspond to optional parameters on the function called.
The following WebIDL defines IDBObjectStoreParameters dictionary type.
dictionary IDBObjectStoreParameters {
DOMString? keyPath = null;
boolean autoIncrement = false;
};
The following WebIDL defines IDBIndexParameters dictionary type.
dictionary IDBIndexParameters {
boolean unique = false;
boolean multiEntry = false;
};
The following WebIDL defines IDBVersionChangeEventInit dictionary type.
dictionary IDBVersionChangeEventInit : EventInit {
unsigned long long oldVersion = 0;
unsigned long long? newVersion = null;
};
When a object store is created it can be specified to use a key generator. A key generator keeps an internal current number. The current number is always a positive integer. Whenever the key generator is used to generate a new key, the generator's current number is returned and then incremented to prepare for the next time a new key is needed. Implementations MUST use the following rules for generating numbers when a key generator is used.
1
when the
object store for that key generator is first created.
1
.
When a record is stored and an key value is specified in the call to store the record,
if the specified key value is a float
greater than or equal to the key generator's
current number, then the key generator's current number is set to the smallest integer
number greater than the explicit key.
A key can be specified both for object stores which use in-line keys, by setting the
property on the stored value which the object store's key path
points to, and for object stores which use out-of-line keys, by passing a key argument
to the call to store the record.
Only specified keys values which
are float
values affect the current number of the key generator.
Date
s and Array
s which contain float
s do not affect
the current number of the key generator. Nor do DOMString
values which
could be parsed as float
s. Likewise, negative float
numbers
do not affect the current number since they are always lower than the current
number.
1
when the key
generator is used, and to modifications that happen due to
2^53
(9007199254740992
) any attempts to use the key generator to generate a new
key will result in an error. It's still possible to insert records into the
object store by specifying an explicit key, however the only way to use a key generator
again for the object store is to delete the object store and create a new one.
Note that as long as key generators are used in a normal fashion this will not be a problem. If you generate a new key 1000 times per second day and night, you won't run into this limit for over 285000 years.
clear()
function, does
not affect the current number of the object store's key generator.
A practical result of this is that the first key generated for an object store is always 1
(unless a higher numeric key is inserted first) and the key generated for an object store is always a
positive integer higher than the highest numeric key in the store. The same key is never generated
twice for the same object store unless a transaction is rolled back.
Each object store gets it's own key generator:
If an insertion fails due to constraint violations or IO error, the key generator is not updated.
Removing items from an objectStore never affects the key generator. Including when .clear() is called.
Inserting an item with an explicit key affects the key generator if, and only if, the key is numeric and higher than the last generated key.
Aborting a transaction rolls back any increases to the key generator which happened during the transaction. This is to make all rollbacks consistent since rollbacks that happen due to crash never has a chance to commit the increased key generator value.