This commit is contained in:
Éric Lemoine
2013-03-11 13:35:17 +01:00
parent 849774dceb
commit f150259eee
1189 changed files with 341774 additions and 2001 deletions
@@ -0,0 +1,175 @@
// Copyright 2012 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Wrapper for a IndexedDB cursor.
*
*/
goog.provide('goog.db.Cursor');
goog.require('goog.async.Deferred');
goog.require('goog.db.Error');
goog.require('goog.debug');
goog.require('goog.events.EventTarget');
/**
* Creates a new IDBCursor wrapper object. Should not be created directly,
* access cursor through object store.
* @see goog.db.ObjectStore#openCursor
*
* @constructor
* @extends {goog.events.EventTarget}
*/
goog.db.Cursor = function() {
goog.base(this);
};
goog.inherits(goog.db.Cursor, goog.events.EventTarget);
/**
* Underlying IndexedDB cursor object.
*
* @type {IDBCursor}
* @private
*/
goog.db.Cursor.prototype.cursor_ = null;
/**
* Advances the cursor to the next position along its direction. When new data
* is availible, the NEW_DATA event will be fired. If the cursor has reached the
* end of the range it will fire the COMPLETE event. If opt_key is specified it
* will advance to the key it matches in its direction.
*
* This wraps the native #continue method on the underlying object.
*
* @param {!Object=} opt_key The optional key to advance to.
*/
goog.db.Cursor.prototype.next = function(opt_key) {
if (opt_key) {
this.cursor_['continue'](opt_key);
} else {
this.cursor_['continue']();
}
};
/**
* Updates the value at the current position of the cursor in the object store.
* If the cursor points to a value that has just been deleted, a new value is
* created.
*
* @param {!Object} value The value to be stored.
* @return {!goog.async.Deferred} The resulting deferred request.
*/
goog.db.Cursor.prototype.update = function(value) {
var msg = 'updating via cursor with value ';
var d = new goog.async.Deferred();
var request;
try {
request = this.cursor_.update(value);
} catch (err) {
msg += goog.debug.deepExpose(value);
d.errback(new goog.db.Error(err.code, msg));
return d;
}
request.onsuccess = function(ev) {
d.callback();
};
request.onerror = function(ev) {
msg += goog.debug.deepExpose(value);
d.errback(new goog.db.Error(
(/** @type {IDBRequest} */ (ev.target)).errorCode, msg));
};
return d;
};
/**
* Deletes the value at the cursor's position, without changing the cursor's
* position. Once the value is deleted, the cursor's value is set to null.
*
* @return {!goog.async.Deferred} The resulting deferred request.
*/
goog.db.Cursor.prototype.remove = function() {
var msg = 'deleting via cursor';
var d = new goog.async.Deferred();
var request;
try {
request = this.cursor_['delete']();
} catch (err) {
d.errback(new goog.db.Error(err.code, msg));
return d;
}
request.onsuccess = function(ev) {
d.callback();
};
request.onerror = function(ev) {
d.errback(new goog.db.Error(
(/** @type {IDBRequest} */ (ev.target)).errorCode, msg));
};
return d;
};
/**
* @return {Object} The value for the value at the cursor's position. Undefined
* if no current value, or null if value has just been deleted.
*/
goog.db.Cursor.prototype.getValue = function() {
return this.cursor_['value'];
};
/**
* @return {*} The key for the value at the cursor's position. If the
* cursor is outside its range, this is undefined.
*/
goog.db.Cursor.prototype.getKey = function() {
return this.cursor_.key;
};
/**
* Possible cursor directions.
* @see http://www.w3.org/TR/IndexedDB/#idl-def-IDBCursor
*
* @enum {number}
*/
goog.db.Cursor.Direction = {
NEXT: 0,
NEXT_NO_DUPLICATE: 1,
PREV: 2,
PREV_NO_DUPLICATE: 3
};
/**
* Event types that the cursor can dispatch. COMPLETE events are dispatched when
* a cursor is depleted of values, a NEW_DATA event if there is new data
* availible, and ERROR if an error occurred.
*
* @enum {string}
*/
goog.db.Cursor.EventType = {
COMPLETE: 'c',
ERROR: 'e',
NEW_DATA: 'n'
};
@@ -0,0 +1,78 @@
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Wrappers for the HTML5 IndexedDB. The wrappers export nearly
* the same interface as the standard API, but return goog.async.Deferred
* objects instead of request objects and use Closure events. The wrapper works
* and has been tested on Chrome version 18+. Though they should work in theory,
* the wrapper tests fail in strange, non-deterministic ways on Firefox 6,
* unfortunately.
*
* Example usage:
*
* <code>
* goog.db.openDatabase('mydb').addCallback(function(db) {
* return db.setVersion('1.0').addCallback(function(tx) {
* db.createObjectStore('mystore');
* // restart to see our structure changes
* return goog.db.openDatabase('mydb');
* });
* }).addCallback(function(db) {
* var putTx = db.createTransaction(
* [],
* goog.db.Transaction.TransactionMode.READ_WRITE);
* var store = putTx.objectStore('mystore');
* store.put('value', 'key');
* goog.listen(putTx, goog.db.Transaction.EventTypes.COMPLETE, function() {
* var getTx = db.createTransaction([]);
* var request = getTx.objectStore('mystore').get('key');
* request.addCallback(function(result) {
* ...
* });
* });
* </code>
*
*/
goog.provide('goog.db');
goog.require('goog.async.Deferred');
goog.require('goog.db.Error');
goog.require('goog.db.IndexedDb');
/**
* Opens a database connection and wraps it.
*
* @param {string} name The name of the database to open.
* @return {!goog.async.Deferred} The deferred database object.
*/
goog.db.openDatabase = function(name) {
var indexedDb = goog.global.indexedDB || goog.global.mozIndexedDB ||
goog.global.webkitIndexedDB || goog.global.moz_indexedDB;
var d = new goog.async.Deferred();
var openRequest = indexedDb.open(name);
openRequest.onsuccess = function(ev) {
var db = new goog.db.IndexedDb(ev.target.result);
d.callback(db);
};
openRequest.onerror = function(ev) {
var msg = 'opening database ' + name;
d.errback(new goog.db.Error(ev.target.code, msg));
};
return d;
};
@@ -0,0 +1,178 @@
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Error classes for the IndexedDB wrapper.
*
*/
goog.provide('goog.db.Error');
goog.provide('goog.db.Error.ErrorCode');
goog.provide('goog.db.Error.VersionChangeBlockedError');
goog.require('goog.debug.Error');
/**
* A database error. Since the stack trace can be unhelpful in an asynchronous
* context, the error provides a message about where it was produced.
*
* @param {number} code The error code.
* @param {string} context A description of where the error occured.
* @param {string=} opt_message Additional message.
* @constructor
* @extends {goog.debug.Error}
*/
goog.db.Error = function(code, context, opt_message) {
var msg = 'Error ' + context + ': ' + goog.db.Error.getMessage(code);
if (opt_message) {
msg += ', ' + opt_message;
}
goog.base(this, msg);
/**
* The code for this error.
*
* @type {number}
*/
this.code = code;
};
goog.inherits(goog.db.Error, goog.debug.Error);
/**
* A specific kind of database error. If a Version Change is unable to proceed
* due to other open database connections, it will block and this error will be
* thrown.
*
* @constructor
* @extends {goog.debug.Error}
*/
goog.db.Error.VersionChangeBlockedError = function() {
goog.base(this, 'Version change blocked');
};
goog.inherits(goog.db.Error.VersionChangeBlockedError, goog.debug.Error);
/**
* Synthetic error codes for database errors, for use when IndexedDB
* support is not available. This numbering differs in practice
* from the browser implementations, but it is not meant to be reliable:
* this object merely ensures that goog.db.Error is loadable on platforms
* that do not support IndexedDB.
*
* @enum {number}
* @private
*/
goog.db.Error.DatabaseErrorCode_ = {
UNKNOWN_ERR: 1,
NON_TRANSIENT_ERR: 2,
NOT_FOUND_ERR: 3,
CONSTRAINT_ERR: 4,
DATA_ERR: 5,
NOT_ALLOWED_ERR: 6,
TRANSACTION_INACTIVE_ERR: 7,
ABORT_ERR: 8,
READ_ONLY_ERR: 9,
TRANSIENT_ERR: 11,
TIMEOUT_ERR: 10,
QUOTA_ERR: 11,
INVALID_ACCESS_ERR: 12
};
/**
* Error codes for database errors.
* @see http://www.w3.org/TR/IndexedDB/#idl-def-IDBDatabaseException
*
* @enum {number}
*/
goog.db.Error.ErrorCode = {
UNKNOWN_ERR: (goog.global.IDBDatabaseException ||
goog.global.webkitIDBDatabaseException ||
goog.db.Error.DatabaseErrorCode_).UNKNOWN_ERR,
NON_TRANSIENT_ERR: (goog.global.IDBDatabaseException ||
goog.global.webkitIDBDatabaseException ||
goog.db.Error.DatabaseErrorCode_).NON_TRANSIENT_ERR,
NOT_FOUND_ERR: (goog.global.IDBDatabaseException ||
goog.global.webkitIDBDatabaseException ||
goog.db.Error.DatabaseErrorCode_).NOT_FOUND_ERR,
CONSTRAINT_ERR: (goog.global.IDBDatabaseException ||
goog.global.webkitIDBDatabaseException ||
goog.db.Error.DatabaseErrorCode_).CONSTRAINT_ERR,
DATA_ERR: (goog.global.IDBDatabaseException ||
goog.global.webkitIDBDatabaseException ||
goog.db.Error.DatabaseErrorCode_).DATA_ERR,
NOT_ALLOWED_ERR: (goog.global.IDBDatabaseException ||
goog.global.webkitIDBDatabaseException ||
goog.db.Error.DatabaseErrorCode_).NOT_ALLOWED_ERR,
TRANSACTION_INACTIVE_ERR: (goog.global.IDBDatabaseException ||
goog.global.webkitIDBDatabaseException ||
goog.db.Error.DatabaseErrorCode_).TRANSACTION_INACTIVE_ERR,
ABORT_ERR: (goog.global.IDBDatabaseException ||
goog.global.webkitIDBDatabaseException ||
goog.db.Error.DatabaseErrorCode_).ABORT_ERR,
READ_ONLY_ERR: (goog.global.IDBDatabaseException ||
goog.global.webkitIDBDatabaseException ||
goog.db.Error.DatabaseErrorCode_).READ_ONLY_ERR,
TIMEOUT_ERR: (goog.global.IDBDatabaseException ||
goog.global.webkitIDBDatabaseException ||
goog.db.Error.DatabaseErrorCode_).TIMEOUT_ERR,
QUOTA_ERR: (goog.global.IDBDatabaseException ||
goog.global.webkitIDBDatabaseException ||
goog.db.Error.DatabaseErrorCode_).QUOTA_ERR,
INVALID_ACCESS_ERR: (goog.global.DOMException ||
goog.db.Error.DatabaseErrorCode_).INVALID_ACCESS_ERR
};
/**
* Translates an error code into a more useful message.
*
* @param {number} code Error code.
* @return {string} A debug message.
*/
goog.db.Error.getMessage = function(code) {
switch (code) {
case goog.db.Error.ErrorCode.UNKNOWN_ERR:
return 'Unknown error';
case goog.db.Error.ErrorCode.NON_TRANSIENT_ERR:
return 'Invalid operation';
case goog.db.Error.ErrorCode.NOT_FOUND_ERR:
return 'Required database object not found';
case goog.db.Error.ErrorCode.CONSTRAINT_ERR:
return 'Constraint unsatisfied';
case goog.db.Error.ErrorCode.DATA_ERR:
return 'Invalid data';
case goog.db.Error.ErrorCode.NOT_ALLOWED_ERR:
return 'Operation disallowed';
case goog.db.Error.ErrorCode.TRANSACTION_INACTIVE_ERR:
return 'Transaction not active';
case goog.db.Error.ErrorCode.ABORT_ERR:
return 'Request aborted';
case goog.db.Error.ErrorCode.READ_ONLY_ERR:
return 'Modifying operation not allowed in a read-only transaction';
case goog.db.Error.ErrorCode.TIMEOUT_ERR:
return 'Transaction timed out';
case goog.db.Error.ErrorCode.QUOTA_ERR:
return 'Database storage space quota exceeded';
case goog.db.Error.ErrorCode.INVALID_ACCESS_ERR:
return 'Invalid operation';
default:
return 'Unrecognized exception with code ' + code;
}
};
@@ -0,0 +1,212 @@
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Wrapper for an IndexedDB index.
*
*/
goog.provide('goog.db.Index');
goog.require('goog.async.Deferred');
goog.require('goog.db.Error');
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
*/
goog.db.Index = function(index) {
/**
* Underlying IndexedDB index object.
*
* @type {!IDBIndex}
* @private
*/
this.index_ = index;
};
/**
* @return {string} Name of the index.
*/
goog.db.Index.prototype.getName = function() {
return this.index_.name;
};
/**
* @return {string} Key path of the index.
*/
goog.db.Index.prototype.getKeyPath = function() {
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() {
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 {!Object} value Value to look up in the index.
* @return {!goog.async.Deferred} The resulting deferred object.
* @private
*/
goog.db.Index.prototype.get_ = function(fn, msg, value) {
var d = new goog.async.Deferred();
var request;
try {
request = this.index_[fn](value);
} catch (err) {
msg += ' with value ' + goog.debug.deepExpose(value);
d.errback(new goog.db.Error(err.code, msg));
return d;
}
request.onsuccess = function(ev) {
d.callback(ev.target.result);
};
request.onerror = function(ev) {
msg += ' with value ' + goog.debug.deepExpose(value);
d.errback(new goog.db.Error(
(/** @type {IDBRequest} */ (ev.target)).errorCode,
msg));
};
return d;
};
/**
* Fetches a single object from the object store. Even if there are multiple
* objects that match the given value, this method will get only one of them.
*
* @param {!Object} value Value to look up in the index.
* @return {!goog.async.Deferred} The deferred object that matches the value.
*/
goog.db.Index.prototype.get = function(value) {
return this.get_('get', 'getting from index ' + this.getName(), value);
};
/**
* 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 objects
* that match the given value, this method will only get one of their keys.
*
* @param {!Object} value Value to look up in the index.
* @return {!goog.async.Deferred} The deferred key for the object that matches
* the value.
*/
goog.db.Index.prototype.getKey = function(value) {
return this.get_('getKey', 'getting key from index ' + this.getName(), value);
};
/**
* Helper function for getAll and getAllKeys.
*
* @param {string} fn Function name to call on the index to get the request.
* @param {string} msg Message to give to the error.
* @param {!Object=} opt_value Value to look up in the index.
* @return {!goog.async.Deferred} The resulting deferred array of objects.
* @private
*/
goog.db.Index.prototype.getAll_ = function(fn, msg, opt_value) {
// This is the most common use of IDBKeyRange. If more specific uses of
// cursors are needed then a full wrapper should be created.
var IDBKeyRange = goog.global.IDBKeyRange || goog.global.webkitIDBKeyRange;
var d = new goog.async.Deferred();
var request;
try {
if (opt_value) {
request = this.index_[fn](IDBKeyRange.bound(opt_value, opt_value));
} else {
request = this.index_[fn]();
}
} catch (err) {
if (opt_value) {
msg += ' for value ' + goog.debug.deepExpose(opt_value);
}
d.errback(new goog.db.Error(err.code, msg));
return d;
}
var result = [];
request.onsuccess = function(ev) {
var cursor = ev.target.result;
if (cursor) {
result.push(cursor.value);
cursor['continue']();
} else {
d.callback(result);
}
};
request.onerror = function(ev) {
if (opt_value) {
msg += ' for value ' + goog.debug.deepExpose(opt_value);
}
d.errback(new goog.db.Error(
(/** @type {IDBRequest} */ (ev.target)).errorCode,
msg));
};
return d;
};
/**
* Gets all indexed objects. If the value is provided, gets all indexed objects
* that match the value instead.
*
* @param {!Object=} opt_value Value to look up in the index.
* @return {!goog.async.Deferred} A deferred array of objects that match the
* value.
*/
goog.db.Index.prototype.getAll = function(opt_value) {
return this.getAll_(
'openCursor',
'getting all from index ' + this.getName(),
opt_value);
};
/**
* Gets the keys to look up all the indexed objects. If the value is provided,
* gets all keys for objects that match the value instead.
*
* @param {!Object=} opt_value Value to look up in the index.
* @return {!goog.async.Deferred} A deferred array of keys for objects that
* match the value.
*/
goog.db.Index.prototype.getAllKeys = function(opt_value) {
return this.getAll_(
'openKeyCursor',
'getting all keys from index ' + this.getName(),
opt_value);
};
@@ -0,0 +1,195 @@
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Wrapper for an IndexedDB database.
*
*/
goog.provide('goog.db.IndexedDb');
goog.require('goog.async.Deferred');
goog.require('goog.db.Error');
goog.require('goog.db.Error.VersionChangeBlockedError');
goog.require('goog.db.ObjectStore');
goog.require('goog.db.Transaction');
goog.require('goog.db.Transaction.TransactionMode');
/**
* Creates an IDBDatabase wrapper object. The database object has methods for
* setting the version to change the structure of the database and for creating
* transactions to get or modify the stored records. Should not be created
* directly, call {@link goog.db.openDatabase} to set up the connection.
*
* @param {!IDBDatabase} db Underlying IndexedDB database object.
* @constructor
*/
goog.db.IndexedDb = function(db) {
/**
* Underlying IndexedDB database object.
*
* @type {!IDBDatabase}
* @private
*/
this.db_ = db;
};
/**
* True iff the database connection is open.
*
* @type {boolean}
* @private
*/
goog.db.IndexedDb.prototype.open_ = true;
/**
* Closes the database connection. Metadata queries can still be made after this
* method is called, but otherwise this wrapper should not be used further.
*/
goog.db.IndexedDb.prototype.close = function() {
if (this.open_) {
this.db_.close();
this.open_ = false;
}
};
/**
* @return {boolean} Whether a connection is open and the database can be used.
*/
goog.db.IndexedDb.prototype.isOpen = function() {
return this.open_;
};
/**
* @return {string} The name of this database.
*/
goog.db.IndexedDb.prototype.getName = function() {
return this.db_.name;
};
/**
* @return {string} The current database version.
*/
goog.db.IndexedDb.prototype.getVersion = function() {
return this.db_.version;
};
/**
* @return {Array} List of object stores in this database.
*/
goog.db.IndexedDb.prototype.getObjectStoreNames = function() {
return this.db_.objectStoreNames;
};
/**
* Creates an object store in this database. Can only be called inside the
* callback for the Deferred returned from #setVersion.
*
* @param {string} name Name for the new object store.
* @param {Object=} opt_params Options object. The available options are:
* keyPath, which is a string and determines what object attribute
* to use as the key when storing objects in this object store; and
* autoIncrement, which is a boolean, which defaults to false and determines
* whether the object store should automatically generate keys for stored
* objects. If keyPath is not provided and autoIncrement is false, then all
* insert operations must provide a key as a parameter.
* @return {goog.db.ObjectStore} The newly created object store.
* @throws {goog.db.Error} If there's a problem creating the object store.
*/
goog.db.IndexedDb.prototype.createObjectStore = function(name, opt_params) {
try {
return new goog.db.ObjectStore(this.db_.createObjectStore(
name, opt_params));
} catch (ex) {
throw new goog.db.Error(ex.code, 'creating object store ' + name);
}
};
/**
* Deletes an object store. Can only be called inside the callback for the
* Deferred returned from #setVersion.
*
* @param {string} name Name of the object store to delete.
* @throws {goog.db.Error} If there's a problem deleting the object store.
*/
goog.db.IndexedDb.prototype.deleteObjectStore = function(name) {
try {
this.db_.deleteObjectStore(name);
} catch (ex) {
throw new goog.db.Error(ex.code, 'deleting object store ' + name);
}
};
/**
* Updates the version of the database and returns a Deferred transaction.
* The database's structure can be changed inside this Deferred's callback, but
* nowhere else. This means adding or deleting object stores, and adding or
* deleting indexes. The version change will not succeed unless there are no
* other connections active for this database anywhere. A new database
* connection should be opened after the version change is finished to pick
* up changes.
*
* @param {string} version The new version of the database.
* @return {!goog.async.Deferred} The deferred transaction for changing the
* version.
*/
goog.db.IndexedDb.prototype.setVersion = function(version) {
var d = new goog.async.Deferred();
var request = this.db_.setVersion(version);
request.onsuccess = function(ev) {
// the transaction is in the result field (the transaction field is null
// for version change requests)
d.callback(new goog.db.Transaction(ev.target.result));
};
request.onerror = function(ev) {
d.errback(new goog.db.Error(ev.target.errorCode, 'setting version'));
};
request.onblocked = function(ev) {
d.errback(new goog.db.Error.VersionChangeBlockedError());
};
return d;
};
/**
* Creates a new transaction.
*
* @param {!Array.<string>} storeNames A list of strings that contains the
* transaction's scope, the object stores that this transaction can operate
* on.
* @param {goog.db.Transaction.TransactionMode=} opt_mode The mode of the
* transaction. If not present, the default is READ_ONLY. For VERSION_CHANGE
* transactions call {@link goog.db.IndexedDB#setVersion} instead.
* @return {!goog.db.Transaction} The wrapper for the newly created transaction.
* @throws {goog.db.Error} If there's a problem creating the transaction.
*/
goog.db.IndexedDb.prototype.createTransaction = function(storeNames, opt_mode) {
try {
return new goog.db.Transaction(this.db_.transaction(storeNames, opt_mode));
} catch (err) {
throw new goog.db.Error(err.code, 'creating transaction');
}
};
@@ -0,0 +1,107 @@
// Copyright 2012 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Wrapper for a IndexedDB key range.
*
*/
goog.provide('goog.db.KeyRange');
/**
* Creates a new IDBKeyRange wrapper object. Should not be created directly,
* instead use one of the static factory methods. For example:
* @see goog.db.KeyRange.bound
* @see goog.db.KeyRange.lowerBound
*
* @param {!IDBKeyRange} range Underlying IDBKeyRange object.
* @constructor
*/
goog.db.KeyRange = function(range) {
/**
* Underlying IDBKeyRange object.
*
* @type {!IDBKeyRange}
* @private
*/
this.range_ = range;
};
/**
* The IDBKeyRange.
* @type {!Object}
* @private
*/
goog.db.KeyRange.IDB_KEY_RANGE_ = goog.global.IDBKeyRange ||
goog.global.webkitIDBKeyRange;
/**
* Creates a new key range for a single value.
*
* @param {Object} key The single value in the range.
* @return {!goog.db.KeyRange} The key range.
*/
goog.db.KeyRange.only = function(key) {
return new goog.db.KeyRange(goog.db.KeyRange.IDB_KEY_RANGE_.only(key));
};
/**
* Creates a key range with upper and lower bounds.
*
* @param {Object} lower The value of the lower bound.
* @param {Object} upper The value of the upper bound.
* @param {boolean=} opt_lowerOpen If true, the range excludes the lower bound
* value.
* @param {boolean=} opt_upperOpen If true, the range excludes the upper bound
* value.
* @return {!goog.db.KeyRange} The key range.
*/
goog.db.KeyRange.bound = function(lower, upper, opt_lowerOpen, opt_upperOpen) {
return new goog.db.KeyRange(goog.db.KeyRange.IDB_KEY_RANGE_.bound(
lower, upper, opt_lowerOpen, opt_upperOpen));
};
/**
* Creates a key range with a lower bound only, finishes at the last record.
*
* @param {Object} lower The value of the lower bound.
* @param {boolean=} opt_lowerOpen If true, the range excludes the lower bound
* value.
* @return {!goog.db.KeyRange} The key range.
*/
goog.db.KeyRange.lowerBound = function(lower, opt_lowerOpen) {
return new goog.db.KeyRange(goog.db.KeyRange.IDB_KEY_RANGE_.lowerBound(
lower, opt_lowerOpen));
};
/**
* Creates a key range with a upper bound only, starts at the first record.
*
* @param {Object} upper The value of the upper bound.
* @param {boolean=} opt_upperOpen If true, the range excludes the upper bound
* value.
* @return {!goog.db.KeyRange} The key range.
*/
goog.db.KeyRange.upperBound = function(upper, opt_upperOpen) {
return new goog.db.KeyRange(goog.db.KeyRange.IDB_KEY_RANGE_.upperBound(
upper, opt_upperOpen));
};
@@ -0,0 +1,401 @@
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Wrapper for an IndexedDB object store.
*
*/
goog.provide('goog.db.ObjectStore');
goog.require('goog.async.Deferred');
goog.require('goog.db.Cursor');
goog.require('goog.db.Error');
goog.require('goog.db.Index');
goog.require('goog.debug');
goog.require('goog.events');
/**
* Creates an IDBObjectStore wrapper object. Object stores have methods for
* storing and retrieving records, and are accessed through a transaction
* object. They also have methods for creating indexes associated with the
* object store. They can only be created when setting the version of the
* database. Should not be created directly, access object stores through
* transactions.
* @see goog.db.IndexedDb#setVersion
* @see goog.db.Transaction#objectStore
*
* @param {!IDBObjectStore} store The backing IndexedDb object.
* @constructor
*/
goog.db.ObjectStore = function(store) {
/**
* Underlying IndexedDB object store object.
*
* @type {!IDBObjectStore}
* @private
*/
this.store_ = store;
};
/**
* @return {string} The name of the object store.
*/
goog.db.ObjectStore.prototype.getName = function() {
return this.store_.name;
};
/**
* Helper function for put and add.
*
* @param {string} fn Function name to call on the object store.
* @param {string} msg Message to give to the error.
* @param {!Object} value Value to insert into the object store.
* @param {!Object=} opt_key The key to use.
* @return {!goog.async.Deferred} The resulting deferred request.
* @private
*/
goog.db.ObjectStore.prototype.insert_ = function(fn, msg, value, opt_key) {
// TODO(user): refactor wrapping an IndexedDB request in a Deferred by
// creating a higher-level abstraction for it (mostly affects here and
// goog.db.Index)
var d = new goog.async.Deferred();
var request;
try {
// put or add with (value, undefined) throws an error, so we need to check
// for undefined ourselves
if (opt_key) {
request = this.store_[fn](value, opt_key);
} else {
request = this.store_[fn](value);
}
} catch (err) {
msg += goog.debug.deepExpose(value);
if (opt_key) {
msg += ', with key ' + goog.debug.deepExpose(opt_key);
}
d.errback(new goog.db.Error(err.code, msg));
return d;
}
request.onsuccess = function(ev) {
d.callback();
};
var self = this;
request.onerror = function(ev) {
msg += goog.debug.deepExpose(value);
if (opt_key) {
msg += ', with key ' + goog.debug.deepExpose(opt_key);
}
d.errback(new goog.db.Error(
(/** @type {IDBRequest} */ (ev.target)).errorCode,
msg));
};
return d;
};
/**
* Adds an object to the object store. Replaces existing objects with the
* same key.
*
* @param {!Object} value The value to put.
* @param {!Object=} opt_key The key to use. Cannot be used if the keyPath was
* specified for the object store. If the keyPath was not specified but
* autoIncrement was not enabled, it must be used.
* @return {!goog.async.Deferred} The deferred put request.
*/
goog.db.ObjectStore.prototype.put = function(value, opt_key) {
return this.insert_(
'put',
'putting into ' + this.getName() + ' with value',
value,
opt_key);
};
/**
* Adds an object to the object store. Requires that there is no object with
* the same key already present.
*
* @param {!Object} value The value to add.
* @param {!Object=} opt_key The key to use. Cannot be used if the keyPath was
* specified for the object store. If the keyPath was not specified but
* autoIncrement was not enabled, it must be used.
* @return {!goog.async.Deferred} The deferred add request.
*/
goog.db.ObjectStore.prototype.add = function(value, opt_key) {
return this.insert_(
'add',
'adding into ' + this.getName() + ' with value ',
value,
opt_key);
};
/**
* Removes an object from the store. No-op if there is no object present with
* the given key.
*
* @param {!Object} key The key to remove objects under.
* @return {!goog.async.Deferred} The deferred remove request.
*/
goog.db.ObjectStore.prototype.remove = function(key) {
var d = new goog.async.Deferred();
var request;
try {
request = this.store_['delete'](key);
} catch (err) {
var msg = 'removing from ' + this.getName() + ' with key ' +
goog.debug.deepExpose(key);
d.errback(new goog.db.Error(err.code, msg));
return d;
}
request.onsuccess = function(ev) {
d.callback();
};
var self = this;
request.onerror = function(ev) {
var msg = 'removing from ' + self.getName() + ' with key ' +
goog.debug.deepExpose(key);
d.errback(new goog.db.Error(
(/** @type {IDBRequest} */ (ev.target)).errorCode,
msg));
};
return d;
};
/**
* Gets an object from the store. If no object is present with that key
* the result is {@code undefined}.
*
* @param {!Object} key The key to look up.
* @return {!goog.async.Deferred} The deferred get request.
*/
goog.db.ObjectStore.prototype.get = function(key) {
var d = new goog.async.Deferred();
var request;
try {
request = this.store_.get(key);
} catch (err) {
var msg = 'getting from ' + this.getName() + ' with key ' +
goog.debug.deepExpose(key);
d.errback(new goog.db.Error(err.code, msg));
return d;
}
request.onsuccess = function(ev) {
d.callback(ev.target.result);
};
var self = this;
request.onerror = function(ev) {
var msg = 'getting from ' + self.getName() + ' with key ' +
goog.debug.deepExpose(key);
d.errback(new goog.db.Error(
(/** @type {IDBRequest} */ (ev.target)).errorCode,
msg));
};
return d;
};
/**
* Gets all objects from the store and returns them as an array.
*
* @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.async.Deferred} The deferred getAll request.
*/
goog.db.ObjectStore.prototype.getAll = function(opt_range, opt_direction) {
var d = new goog.async.Deferred();
var cursor;
try {
cursor = this.openCursor(opt_range, opt_direction);
} catch (err) {
d.errback(err);
return d;
}
var result = [];
var key = goog.events.listen(
cursor, goog.db.Cursor.EventType.NEW_DATA, function() {
result.push(cursor.getValue());
cursor.next();
});
goog.events.listenOnce(cursor, [
goog.db.Cursor.EventType.ERROR,
goog.db.Cursor.EventType.COMPLETE
], function(evt) {
goog.events.unlistenByKey(key);
if (evt.type == goog.db.Cursor.EventType.COMPLETE) {
d.callback(result);
} else {
d.errback();
}
});
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 = objectStore.openCursor(goog.db.Range.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.
* @suppress {accessControls}
*/
goog.db.ObjectStore.prototype.openCursor = function(opt_range, opt_direction) {
var msg = 'opening cursor ' + this.getName();
var cursor = new goog.db.Cursor();
var request;
try {
var range = opt_range ? opt_range.range_ : null;
if (opt_direction) {
request = this.store_.openCursor(range, opt_direction);
} else {
request = this.store_.openCursor(range);
}
} catch (err) {
throw new goog.db.Error(err.code, msg);
}
request.onsuccess = function(ev) {
cursor.cursor_ = ev.target.result || null;
if (cursor.cursor_) {
cursor.dispatchEvent(goog.db.Cursor.EventType.NEW_DATA);
} else {
cursor.dispatchEvent(goog.db.Cursor.EventType.COMPLETE);
}
};
request.onerror = function(ev) {
cursor.dispatchEvent(goog.db.Cursor.EventType.ERROR);
};
return cursor;
};
/**
* Deletes all objects from the store.
*
* @return {!goog.async.Deferred} The deferred clear request.
*/
goog.db.ObjectStore.prototype.clear = function() {
var msg = 'clearing store ' + this.getName();
var d = new goog.async.Deferred();
var request;
try {
request = this.store_.clear();
} catch (err) {
d.errback(new goog.db.Error(err.code, msg));
return d;
}
request.onsuccess = function(ev) {
d.callback();
};
request.onerror = function(ev) {
d.errback(new goog.db.Error(
(/** @type {IDBRequest} */ (ev.target)).errorCode,
msg));
};
return d;
};
/**
* Creates an index in this object store. Can only be called inside the callback
* for the Deferred returned from goog.db.IndexedDb#setVersion.
*
* @param {string} name Name of the index to create.
* @param {string} keyPath Attribute to index on.
* @param {!Object=} opt_parameters Optional parameters object. The only
* available option is unique, which defaults to false. If unique is true,
* the index will enforce that there is only ever one object in the object
* store for each unique value it indexes on.
* @return {goog.db.Index} The newly created, wrapped index.
* @throws {goog.db.Error} In case of an error creating the index.
*/
goog.db.ObjectStore.prototype.createIndex = function(
name, keyPath, opt_parameters) {
try {
return new goog.db.Index(this.store_.createIndex(
name, keyPath, opt_parameters));
} catch (err) {
var msg = 'creating new index ' + name + ' with key path ' + keyPath;
throw new goog.db.Error(err.code, msg);
}
};
/**
* Gets an index.
*
* @param {string} name Name of the index to fetch.
* @return {goog.db.Index} The requested wrapped index.
* @throws {goog.db.Error} In case of an error getting the index.
*/
goog.db.ObjectStore.prototype.getIndex = function(name) {
try {
return new goog.db.Index(this.store_.index(name));
} catch (err) {
var msg = 'getting index ' + name;
throw new goog.db.Error(err.code, msg);
}
};
/**
* Deletes an index from the object store. Can only be called inside the
* callback for the Deferred returned from goog.db.IndexedDb#setVersion.
*
* @param {string} name Name of the index to delete.
* @throws {goog.db.Error} In case of an error deleting the index.
*/
goog.db.ObjectStore.prototype.deleteIndex = function(name) {
try {
this.store_.deleteIndex(name);
} catch (err) {
var msg = 'deleting index ' + name;
throw new goog.db.Error(err.code, msg);
}
};
@@ -0,0 +1,175 @@
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Wrapper for an IndexedDB transaction.
*
*/
goog.provide('goog.db.Transaction');
goog.provide('goog.db.Transaction.TransactionMode');
goog.require('goog.db.Error');
goog.require('goog.db.ObjectStore');
goog.require('goog.events.EventHandler');
goog.require('goog.events.EventTarget');
/**
* Creates a new transaction. Transactions contain methods for accessing object
* stores and are created from the database object. Should not be created
* directly, open a database and call createTransaction on it.
* @see goog.db.IndexedDb#createTransaction
*
* @param {!IDBTransaction} tx IndexedDB transaction to back this wrapper.
* @constructor
* @extends {goog.events.EventTarget}
*/
goog.db.Transaction = function(tx) {
goog.base(this);
/**
* Underlying IndexedDB transaction object.
*
* @type {!IDBTransaction}
* @private
*/
this.tx_ = tx;
/**
* Event handler for this transaction.
*
* @type {!goog.events.EventHandler}
* @private
*/
this.eventHandler_ = new goog.events.EventHandler(this);
// TODO(user): remove these casts once the externs file is updated to
// correctly reflect that IDBTransaction extends EventTarget
this.eventHandler_.listen(
(/** @type {EventTarget} */ this.tx_),
'complete',
goog.bind(
this.dispatchEvent,
this,
goog.db.Transaction.EventTypes.COMPLETE));
this.eventHandler_.listen(
(/** @type {EventTarget} */ this.tx_),
'abort',
goog.bind(
this.dispatchEvent,
this,
goog.db.Transaction.EventTypes.ABORT));
this.eventHandler_.listen(
(/** @type {EventTarget} */ this.tx_),
'error',
this.dispatchError_);
};
goog.inherits(goog.db.Transaction, goog.events.EventTarget);
/**
* Dispatches an error event based on the given event, wrapping the error
* if necessary.
*
* @param {Event} ev The error event given to the underlying IDBTransaction.
* @private
*/
goog.db.Transaction.prototype.dispatchError_ = function(ev) {
if (ev.target instanceof goog.db.Error) {
this.dispatchEvent({
type: goog.db.Transaction.EventTypes.ERROR,
target: ev.target
});
} else {
this.dispatchEvent({
type: goog.db.Transaction.EventTypes.ERROR,
target: new goog.db.Error(
(/** @type {IDBRequest} */ (ev.target)).errorCode,
'in transaction')
});
}
};
/**
* Event types the Transaction can dispatch. COMPLETE events are dispatched
* when the transaction is committed. If a transaction is aborted it dispatches
* both an ABORT event and an ERROR event with the ABORT_ERR code. Error events
* are dispatched on any error.
*
* @enum {string}
*/
goog.db.Transaction.EventTypes = {
COMPLETE: 'complete',
ABORT: 'abort',
ERROR: 'error'
};
/**
* @return {goog.db.Transaction.TransactionMode} The transaction's mode.
*/
goog.db.Transaction.prototype.getMode = function() {
return /** @type {goog.db.Transaction.TransactionMode} */ (this.tx_.mode);
};
/**
* Opens an object store to do operations on in this transaction. The requested
* object store must be one that is in this transaction's scope.
* @see goog.db.IndexedDb#createTransaction
*
* @param {string} name The name of the requested object store.
* @return {!goog.db.ObjectStore} The wrapped object store.
* @throws {goog.db.Error} In case of error getting the object store.
*/
goog.db.Transaction.prototype.objectStore = function(name) {
try {
return new goog.db.ObjectStore(this.tx_.objectStore(name));
} catch (err) {
throw new goog.db.Error(err.code, 'getting object store ' + name);
}
};
/**
* Aborts this transaction. No pending operations will be applied to the
* database. Dispatches an ABORT event.
*/
goog.db.Transaction.prototype.abort = function() {
this.tx_.abort();
};
/** @override */
goog.db.Transaction.prototype.disposeInternal = function() {
goog.base(this, 'disposeInternal');
this.eventHandler_.dispose();
};
/**
* The three possible transaction modes.
* @see http://www.w3.org/TR/IndexedDB/#idl-def-IDBTransaction
*
* @enum {number}
*/
goog.db.Transaction.TransactionMode = {
READ_ONLY: 0,
READ_WRITE: 1,
VERSION_CHANGE: 2
};