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

Constructs

Database

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.

Object Store

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:

  1. A key generator. A key generator generates a monotonically increasing numbers every time a key is needed.
  2. Keys can be derived via a key path.
  3. Keys can also be explicitly specified when a value is stored in the object store.

The IDBObjectStore and IDBObjectStoreSync interfaces represent an object store. Note however that multiple instances of those interfaces representing the same object store can exist.

Keys

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 Arrays 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 Arrays.

For purposes of comparison, all Arrays 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:

  1. Let A be the first Array value and B be the second Array value.
  2. Let length be the lesser of A's length and B's length.
  3. Let i be 0.
  4. If the ith value of A is less than the ith value of B, then A is less than B. Skip the remaining steps.
  5. If the ith value of A is greater than the ith value of B, then A is greater than B. Skip the remaining steps.
  6. Increase i by 1.
  7. If i is not equal to length, go back to step 4. Otherwise continue to next step.
  8. If A's length is less than B's length, then A is less than B. If A's length is greater than B's length, then A is greater than B. Otherwise A and B are equal.

Note that Arrays that contain other Arrays 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.

"foo.bar" { foo: {} }

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.

"foo.bar" { foo: { bar: 10 } }

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.

"foo.bar.baz" { zip: {} }

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.

// The key generation will attempt to create and store the key path property on this primitive. "foo" 4 // The key generation will attempt to create and store the key path property on this array. "foo" [10]

Values

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.

Key Path

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:

  • An empty DOMString.
  • A DOMString containing a JavaScript identifier [[!ECMA-262]].
  • A DOMString containing multiple Javascript identifiers separated by periods (ASCII character code 46) [[!ECMA-262]].
  • A non-empty Array containing only DOMStrings 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

Index

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.

Transaction

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:

  1. A transaction is created using IDBDatabase.transaction. The arguments passed determine the scope of the transaction and whether the transaction is read-only. When a transaction is created its active flag is initially set to true.
  2. The implementation MUST allow requests to be placed against the transaction whenever the active flag is true. This is the case even if the transaction has not yet been started. Until the transaction is started the implementation MUST NOT execute these requests; however, the implementation MUST keep track of the requests and their order. Requests may be placed against a transaction only while that transaction is active. If an attempt is made to place a request against a transaction when that transaction is not active, the implementation MUST reject the attempt by throwing a DOMException of type TransactionInactiveError.
  3. Once an implementation is able to enforce the constraints defined for the transaction mode, defined below, the implementation MUST queue up an operation to start the transaction asynchronously. The timing for when this happens is affected by:
    • The mode in which the transaction is opened.
    • Which object stores are included in the scope of the transaction.
  4. Once the transaction has been started the implementation can start executing the requests placed against the transaction. Unless otherwise defined, requests MUST be executed in the order in which they were made against the transaction. Likewise, their results MUST be returned in the order the requests were placed against a specific transaction. There is no guarantee about the order that results from requests in different transactions are returned. Similarly, the transaction modes ensure that two requests placed against different transactions can execute in any order without affecting what resulting data is stored in the database.
  5. A transaction can be aborted at any time before it is finished, even if the transaction isn't currently active or hasn't yet started. When a transaction is aborted the implementation MUST undo (roll back) any changes that were made to the database during that transaction. This includes both changes to the contents of object stores as well as additions and removals of object stores and indexes.
  6. A transaction can fail for reasons not tied to a particular DOMRequest. For example due to IO errors when committing the transaction, or due to running into a quota limit where the implementation can't tie exceeding the quota to a partcular request. In this case the implementation MUST run the steps for aborting a transaction using the transaction as transaction and the appropriate error type as error. For example if quota was exceeded then QuotaExceededError should be used as error, and if an IO error happened, UnknownError should be used as error.
  7. When a transaction can no longer become active, the implementation MUST attempt to commit it, as long as the transaction has not been aborted. This usually happens after all requests placed against the transaction have been executed and their returned results handled, and no new requests have been placed against the transaction. When a transaction is committed, the implementation MUST atomically write any changes to the database made by requests placed against the transaction. That is, either all of the changes MUST be written, or if an error occurs, such as a disk write error, the implementation MUST NOT write any of the changes to the database. If such an error occurs, the implementation MUST abort the transaction by following the steps for aborting a transaction, otherwise it MUST commit the transaction by following the steps for committing a transaction.
  8. When a transaction is committed or aborted, it is said to be finished. If a transaction can't be finished, for example due to the implementation crashing or the user taking some explicit action to cancel it, the implementation MUST abort the transaction.

Transactions are opened in one of three modes. The mode determines how concurrent access to object stores in the transaction are isolated.

  1. "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.
  2. "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.
  3. "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.

Requests

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.

Key Range

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.

readonly attribute any lower
This value is the lower-bound of the key range.
readonly attribute any upper
This value is the upper-bound of the key range.
readonly attribute boolean lowerOpen
Returns false if the lower-bound value is included in the key range. Returns true if the lower-bound value is excluded from the key range.
readonly attribute boolean upperOpen
Returns false if the upper-bound value is included in the key range. Returns true if the upper-bound value is excluded from the key range.
IDBKeyRange only()
Creates and returns a new key range with both lower and upper set to value and both lowerOpen and upperOpen set to false.
any value
The only value
DataError
The value parameter was not passed a valid key.
IDBKeyRange lowerBound()
Creates and returns a new key range with lower set to lower, lowerOpen set to open, upper set to undefined and and upperOpen set to true.
any lower
The lower bound value
optional boolean open
Set to false if the lower-bound should be included in the key range. Set to true if the lower-bound value should be excluded from the key range. Defaults to false (lower-bound value is included).
DataError
The value parameter was not passed a valid key.
IDBKeyRange upperBound()
Creates and returns a new key range with lower set to undefined, lowerOpen set to true, upper set to upper and and upperOpen set to open.
any upper
The upper bound value
optional boolean open
Set to false if the upper-bound should be included in the key range. Set to true if the upper-bound value should be excluded from the key range. Defaults to false (upper-bound value is included).
DataError
The value parameter was not passed a valid key.
IDBKeyRange bound()
Creates and returns a new key range with lower set to lower, lowerOpen set to lowerOpen, upper set to upper and upperOpen set to upperOpen.
any lower
The lower-bound value
any upper
The upper-bound value
optional boolean lowerOpen
Set to false if the lower-bound should be included in the key range. Set to true if the lower-bound value should be excluded from the key range. Defaults to false (lower-bound value is included).
optional boolean upperOpen
Set to false if the upper-bound should be included in the key range. Set to true if the upper-bound value should be excluded from the key range. Defaults to false (upper-bound value is included).
DataError
Either the lower or upper parameters were not passed a valid key or the lower key is greater than the upper key or the lower key and upper key match and either of the bounds are open.

A key is in a key range if both the following conditions are fulfilled:

Cursor

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.

Exceptions

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 Object

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.

IDL

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;
};
                
              

Key Generators

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.

  • Every object store that uses key generators use a seprate generator. I.e. interacting with one object store never affects the key generator of any other object store.
  • The current number of a key generator is always set to 1 when the object store for that key generator is first created.
  • When a key generator is used to generate a new key for a object store, the key generator's current number is used as the new key value and then the key generator's current number is increased by 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. Dates and Arrays which contain floats do not affect the current number of the key generator. Nor do DOMString values which could be parsed as floats. Likewise, negative float numbers do not affect the current number since they are always lower than the current number.

  • Modifying a key generator's current number is considered part of a database operation. This means that if the operation fails and the operation is reverted, the current number is reverted to the value it had before the operation started. This applies both to modifications that happen due to the current number getting increased by 1 when the key generator is used, and to modifications that happen due to
  • Likewise, if a transaction is aborted, the current number of the key generator's for all object stores in the transaction's scope is reverted to the values they had before the transaction was started.
  • When the current number of a key generator reaches above the value 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.

  • The current number for a key generator never decreases, other than as a result of database operations being reverted. Deleting a record from an object store never affects the object store's key generator. Even clearing all records from an object store, for example using the 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:

store1 = db.createObjectStore("store1", { autoIncrement: true }); store1.put("a"); // Will get key 1 store2 = db.createObjectStore("store2", { autoIncrement: true }); store2.put("a"); // Will get key 1 store1.put("b"); // Will get key 2 store2.put("b"); // Will get key 2

If an insertion fails due to constraint violations or IO error, the key generator is not updated.

transaction.onerror = function(e) { e.preventDefault() }; store = db.createObjectStore("store1", { autoIncrement: true }); index = store.createIndex("index1", "ix", { unique: true }); store.put({ ix: "a"}); // Will get key 1 store.put({ ix: "a"}); // Will fail store.put({ ix: "b"}); // Will get key 2

Removing items from an objectStore never affects the key generator. Including when .clear() is called.

store = db.createObjectStore("store1", { autoIncrement: true }); store.put("a"); // Will get key 1 store.delete(1); store.put("b"); // Will get key 2 store.clear(); store.put("c"); // Will get key 3 store.delete(IDBKeyRange.lowerBound(0)); store.put("d"); // Will get key 4

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.

store = db.createObjectStore("store1", { autoIncrement: true }); store.put("a"); // Will get key 1 store.put("b", 3); // Will use key 3 store.put("c"); // Will get key 4 store.put("d", -10); // Will use key -10 store.put("e"); // Will get key 5 store.put("f", 6.00001); // Will use key 6.0001 store.put("g"); // Will get key 7 store.put("f", 8.9999); // Will use key 8.9999 store.put("g"); // Will get key 9 store.put("h", "foo"); // Will use key "foo" store.put("i"); // Will get key 10 store.put("j", [1000]); // Will use key [1000] store.put("k"); // Will get key 11 // All of these would behave the same if the objectStore used a keyPath and the explicit key was passed inline in the object

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.

db.createObjectStore("store", { autoIncrement: true }); trans1 = db.transaction(["store"], "readwrite"); store_t1 = trans1.objectStore("store"); store_t1.put("a"); // Will get key 1 store_t1.put("b"); // Will get key 2 trans1.abort(); trans2 = db.transaction(["store"], "readwrite"); store_t2 = trans2.objectStore("store"); store_t2.put("c"); // Will get key 1 store_t2.put("d"); // Will get key 2