2.11. Key Generators

When a object store is created it can be specified to use a key generator. A key generator is used to generate keys for records inserted into an object store if not otherwise specified.

A key generator has a current number. The current number is always a positive integer less than or equal to 253 (9007199254740992) + 1. The initial value of a key generator‘s current number is 1, set when the associated object store is created. The current number is incremented as keys are generated, and may be updated to a specific value by using explicit keys.

Every object store that uses key generators uses a separate generator. That is, interacting with one object store never affects the key generator of any other object store.

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 a record being stored with a key value specified in the call to store the record.

Likewise, if a transaction is aborted, the current number of the key generator for each object store in the transaction’s scope is reverted to the value it had before the transaction was started.

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()](#dom-idbobjectstore-clear) method, does not affect the current number of the object store’s key generator.

When a record is stored and a key is not specified in the call to store the record, a key is generated.

To generate a key for an object store store, run these steps:

  1. Let generator be the key generator associated with store.

  2. Let key be generator’s current number.

  3. If key is greater than 253 (9007199254740992), then return failure.

  4. Increase generator’s current number by 1.

  5. Return key.

When a record is stored and a key is specified in the call to store the record, the associated key generator may be updated.

To possibly update the key generator for an object store store with key, run these steps:

  1. If the type of key is not number, abort these steps.

  2. Let value be the value of key.

  3. Let value be the minimum of value and 253 (9007199254740992).

  4. Let value be the largest integer not greater than value.

  5. Let generator be the key generator associated with store.

  6. If value is greater than or equal to generator’s current number, then set generator’s current number to value + 1.

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 of type number can affect the current number of the key generator. Keys of type date, array (regardless of the other keys they contain), binary, or string (regardless of whether they could be parsed as numbers) have no effect on the current number of the key generator. Keys of type number with value less than 1 do not affect the current number since they are always lower than the current number.

When the current number of a key generator reaches above the value 253 (9007199254740992) any subsequent attempts to use the key generator to generate a new key will result in a “[ConstraintError](https://www.w3.org/TR/WebIDL-1/#constrainterror)[DOMException](https://www.w3.org/TR/WebIDL-1/#idl-DOMException). It is 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 such records is to delete the object store and create a new one.

This limit arises because integers greater than 9007199254740992 cannot be uniquely represented as ECMAScript Numbers. As an example, 9007199254740992 + 1 === 9007199254740992 in ECMAScript.

As long as key generators are used in a normal fashion this limit 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.

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 its own key generator:

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

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

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

Removing items from an objectStore never affects the key generator. Including when [clear()](#dom-idbobjectstore-clear) is called.

  1. store = db.createObjectStore("store1", { autoIncrement: true });
  2. store.put("a"); // Will get key 1
  3. store.delete(1);
  4. store.put("b"); // Will get key 2
  5. store.clear();
  6. store.put("c"); // Will get key 3
  7. store.delete(IDBKeyRange.lowerBound(0));
  8. 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.

  1. store = db.createObjectStore("store1", { autoIncrement: true });
  2. store.put("a"); // Will get key 1
  3. store.put("b", 3); // Will use key 3
  4. store.put("c"); // Will get key 4
  5. store.put("d", -10); // Will use key -10
  6. store.put("e"); // Will get key 5
  7. store.put("f", 6.00001); // Will use key 6.0001
  8. store.put("g"); // Will get key 7
  9. store.put("f", 8.9999); // Will use key 8.9999
  10. store.put("g"); // Will get key 9
  11. store.put("h", "foo"); // Will use key "foo"
  12. store.put("i"); // Will get key 10
  13. store.put("j", [1000]); // Will use key [1000]
  14. store.put("k"); // Will get key 11
  15. // All of these would behave the same if the objectStore used a
  16. // 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.

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

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 1 because that is the next key generated by the key generator.

  1. var store = db.createObjectStore("store", { keyPath: "foo.bar",
  2. autoIncrement: true });
  3. store.put({ foo: {} }).onsuccess = function(e) {
  4. var key = e.target.result;
  5. console.assert(key === 1);
  6. };

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 key path 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.

  1. var store = db.createObjectStore("store", { keyPath: "foo.bar",
  2. autoIncrement: true });
  3. store.put({ foo: { bar: 10 } }).onsuccess = function(e) {
  4. var key = e.target.result;
  5. console.assert(key === 10);
  6. };

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.

  1. var store = db.createObjectStore("store", { keyPath: "foo.bar.baz",
  2. autoIncrement: true });
  3. store.put({ zip: {} }).onsuccess = function(e) {
  4. var key = e.target.result;
  5. console.assert(key === 1);
  6. store.get(key).onsuccess = function(e) {
  7. var value = e.target.result;
  8. // value will be: { zip: {}, foo: { bar: { baz: 1 } } }
  9. console.assert(value.foo.bar.baz === 1);
  10. };
  11. };

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.

  1. var store = db.createObjectStore("store", { keyPath: "foo", autoIncrement: true });
  2.  
  3. // The key generation will attempt to create and store the key path
  4. // property on this primitive.
  5. store.put(4); // will throw DataError
  6.  
  7. // The key generation will attempt to create and store the key path
  8. // property on this array.
  9. store.put([10]); // will throw DataError