/**
* @license
* Copyright The Closure Library Authors.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview Wrapper for an IndexedDB index.
*/
goog.provide('goog.db.Index');
goog.require('goog.async.Deferred');
goog.require('goog.db.Cursor');
goog.require('goog.db.Error');
goog.require('goog.db.KeyRange');
goog.require('goog.debug');
/**
* Creates an IDBIndex wrapper object. Indexes are associated with object
* stores and provide methods for looking up objects based on their non-key
* properties. Should not be created directly, access through the object store
* it belongs to.
* @see goog.db.ObjectStore#getIndex
*
* @param {!IDBIndex} index Underlying IDBIndex object.
* @constructor
* @final
*/
goog.db.Index = function(index) {
'use strict';
/**
* Underlying IndexedDB index object.
*
* @type {!IDBIndex}
* @private
*/
this.index_ = index;
};
/**
* @return {string} Name of the index.
*/
goog.db.Index.prototype.getName = function() {
'use strict';
return this.index_.name;
};
/**
* @return {*} Key path of the index.
*/
goog.db.Index.prototype.getKeyPath = function() {
'use strict';
return this.index_.keyPath;
};
/**
* @return {boolean} True if the index enforces that there is only one object
* for each unique value it indexes on.
*/
goog.db.Index.prototype.isUnique = function() {
'use strict';
return this.index_.unique;
};
/**
* Helper function for get and getKey.
*
* @param {string} fn Function name to call on the index to get the request.
* @param {string} msg Message to give to the error.
* @param {!IDBKeyType} key The key to look up in the index.
* @return {!goog.async.Deferred} The resulting deferred object.
* @private
*/
goog.db.Index.prototype.get_ = function(fn, msg, key) {
'use strict';
const d = new goog.async.Deferred();
let request;
try {
request = this.index_[fn](key);
} catch (err) {
msg += ' with key ' + goog.debug.deepExpose(key);
d.errback(goog.db.Error.fromException(err, msg));
return d;
}
request.onsuccess = function(ev) {
'use strict';
d.callback(ev.target.result);
};
request.onerror = function(ev) {
'use strict';
msg += ' with key ' + goog.debug.deepExpose(key);
d.errback(goog.db.Error.fromRequest(ev.target, msg));
};
return d;
};
/**
* Fetches a single object from the object store. Even if there are multiple
* objects that match the given key, this method will get only one of them.
*
* @param {!IDBKeyType} key Key to look up in the index.
* @return {!goog.async.Deferred} The deferred object for the given record.
*/
goog.db.Index.prototype.get = function(key) {
'use strict';
return this.get_('get', 'getting from index ' + this.getName(), key);
};
/**
* Looks up a single object from the object store and gives back the key that
* it's listed under in the object store. Even if there are multiple records
* that match the given key, this method returns the first.
*
* @param {!IDBKeyType} key Key to look up in the index.
* @return {!goog.async.Deferred} The deferred key for the record that matches
* the key.
*/
goog.db.Index.prototype.getKey = function(key) {
'use strict';
return this.get_('getKey', 'getting key from index ' + this.getName(), key);
};
/**
* Returns the values matching `opt_key` up to `opt_count`.
*
* If `obt_key` is a `KeyRange`, returns all keys in that range. If it is
* `undefined`, returns all known keys.
*
* @param {!IDBKeyType|!goog.db.KeyRange=} opt_key Key or KeyRange to look up in
* the index.
* @param {number=} opt_count The number records to return
* @return {!goog.async.Deferred} A deferred array of objects that match the
* key.
*/
goog.db.Index.prototype.getAll = function(opt_key, opt_count) {
'use strict';
return this.getAll_(
'getAll', 'getting all from index ' + this.getName(), opt_key, opt_count);
};
/**
* Returns the keys matching `opt_key` up to `opt_count`.
*
* If `obt_key` is a `KeyRange`, returns all keys in that range. If it is
* `undefined`, returns all known keys.
*
* @param {!IDBKeyType|!goog.db.KeyRange=} opt_key Key or KeyRange to look up in
* the index.
* @param {number=} opt_count The number records to return
* @return {!goog.async.Deferred} A deferred array of keys for objects that
* match the key.
*/
goog.db.Index.prototype.getAllKeys = function(opt_key, opt_count) {
'use strict';
return this.getAll_(
'getAllKeys', 'getting all keys index ' + this.getName(), opt_key,
opt_count);
};
/**
* Helper function for native `getAll` and `getAllKeys` on `IDBObjectStore` that
* takes in `IDBKeyRange` as params.
*
* Returns the result of the native method in a `Deferred` object.
*
* @param {string} fn Function name to call on the index to get the request.
* @param {string} msg Message to give to the error.
* @param {!IDBKeyType|!goog.db.KeyRange|undefined} keyOrRange
* Key or KeyRange to look up in the index.
* @param {number|undefined} count The number records to return
* @return {!goog.async.Deferred} The resulting deferred array of objects.
* @private
*/
goog.db.Index.prototype.getAll_ = function(fn, msg, keyOrRange, count) {
'use strict';
let nativeRange;
if (keyOrRange === undefined) {
nativeRange = undefined;
} else if (keyOrRange instanceof goog.db.KeyRange) {
nativeRange = keyOrRange.range();
} else {
nativeRange = goog.db.KeyRange.only(keyOrRange).range();
}
const d = new goog.async.Deferred();
let request;
try {
request = this.index_[fn](nativeRange, count);
} catch (err) {
msg += ' for range ' +
(nativeRange ? goog.debug.deepExpose(nativeRange) : '<all>');
d.errback(goog.db.Error.fromException(err, msg));
return d;
}
request.onsuccess = function() {
'use strict';
d.callback(request.result);
};
request.onerror = function(ev) {
'use strict';
msg += ' for range ' +
(nativeRange ? goog.debug.deepExpose(nativeRange) : '<all>');
d.errback(goog.db.Error.fromRequest(ev.target, msg));
};
return d;
};
/**
* Opens a cursor over the specified key range. Returns a cursor object which is
* able to iterate over the given range.
*
* Example usage:
*
* <code>
* var cursor = index.openCursor(goog.db.KeyRange.bound('a', 'c'));
*
* var key = goog.events.listen(
* cursor, goog.db.Cursor.EventType.NEW_DATA,
* function() {
* // Do something with data.
* cursor.next();
* });
*
* goog.events.listenOnce(
* cursor, goog.db.Cursor.EventType.COMPLETE,
* function() {
* // Clean up listener, and perform a finishing operation on the data.
* goog.events.unlistenByKey(key);
* });
* </code>
*
* @param {!goog.db.KeyRange=} opt_range The key range. If undefined iterates
* over the whole object store.
* @param {!goog.db.Cursor.Direction=} opt_direction The direction. If undefined
* moves in a forward direction with duplicates.
* @return {!goog.db.Cursor} The cursor.
* @throws {!goog.db.Error} If there was a problem opening the cursor.
*/
goog.db.Index.prototype.openCursor = function(opt_range, opt_direction) {
'use strict';
return goog.db.Cursor.openCursor(this.index_, opt_range, opt_direction);
};