Source: lib/Stork.js



/**
 * Creates a Stork instance.
 *
 * ```javascript
 * new Stork(); // global key-values/records
 * new Stork({name: 'todos'}); // grouped key-values/records
 * new Stork({name: 'rooms', key: 'ID'}); // records have 'ID' property which is used as key for saving records
 * new Stork({name: 'you are', lazy: true}); // records aren't all loaded on start, they are loaded as needed
 * new Stork({name: 'users', database: 'myapp', size: 65536}); // some storage engines support a custom database name and a desired size for the database
 *
 * new Stork(options, function(stork) {
 *   // stork = initialized stork instance
 * });
 * ```
 *
 * @constructor
 * @class
 * @param {Object} [options]
 *        An object of options, see the following properties for more details:
 *        {@link Stork#key}, {@link Stork#name}, {@link Stork#lazy}.
 * @param {Stork~initSuccess} [success]
 *        The function to invoke when the instance successfully initializes.
 * @param {Stork~initFailure} [failure]
 *        The function to invoke if this instance failes to initialize.
 */
function Stork(options, success, failure)
{
  // If this wasn't called as a constructor, return an instance!
  if (!(this instanceof Stork)) return new Stork( options, success, failure );

  // JSON is required for StorkJS
  if (!JSON) throw 'JSON unavailable! Include http://www.json.org/json2.js to fix.';

  /**
   * The options passed to the constructor and subsequently to the 
   * {@link Stork#init} function.
   * 
   * @type {Object}
   * @default  {}
   */
  this.options = options = (options || {});

  /**
   * The name of the property to use as the key for the 
   * {@link Stork#save} and {@link Stork#batch} functions. This should 
   * be specified in the `options` object.
   * 
   * @type {String}
   * @default 'id'
   */
  this.key = coalesce( options.key, 'id' );

  /**
   * The name used to group the key-value pairs. This is essentially
   * a table name. This should be specified in the `options` object.
   *           
   * @type {String}
   * @default ''
   */
  this.name = coalesce( options.name, '' );

  /**
   * If true, key-value pairs will be lazily loaded instead of loaded
   * all at once on initialization. This should be specified in the 
   * `options` object.
   *           
   * @type {Boolean}
   * @default false
   */
  this.lazy = coalesce( options.lazy, false );

  /**
   * The cache of key-value pairs currently loaded. If 
   * {@link Stork#loaded} is true then all key-value pairs exist in
   * the cache.
   *           
   * @type {FastMap}
   */
  this.cache = new FastMap();

  /**
   * An array of functions called by the user before this instances
   * was finished initializing. Once this instance successfully finishes 
   * initialization all pending functions are invoked in the order
   * in which they were originally made and this property is set to
   * `null`.
   *           
   * @type {Object[]}
   */
  this.pending = [];

  /**
   * True if this instance has successfully initialized, otherwise
   * false if it failed to initialize or has not finished initializing.
   *           
   * @type {Boolean}
   */
  this.initialized = false;

  /**
   * True if the entire instance has been loaded into the 
   * {@link Stork#cache}, otherwise false. If lazy is specifed as true
   * loaded will be false until any of the following methods are
   * invoked: {@link Stork#each}, {@link Stork#all}, or
   * {@link Stork#reload}.
   *           
   * @type {Boolean}
   */
  this.loaded = false;

  /**
   * The adapter `Object` with `String` name, `Number` priority, and
   * `Object` definition properties. The adapter can be chosen based
   * on the `options.adapter` and falls back to the next supported
   * adapter based on priority.
   *           
   * @type {Object}
   */
  this.adapter = getAdapter( options.adapter );

  // Copy the chosen adapter methods into this instance.
  copy( this.adapter.definition, this );
  
  // Call each plugin on this instance before initialization starts.
  for (var i = 0; i < Stork.plugins.length; i++)
  {
    Stork.plugins[ i ]( this );
  }

  // Start initializaing this instance.
  this.initializing = this.init( this.options, success, failure );
}

Stork.prototype = 
{

  /**
   * Decodes a key from a string.
   *
   * @method decode
   * @param {String} rawKey
   *        The string to decode into a key.
   * @return {Any}
   */
  decode: fromJson,

  /**
   * Encodes a key into a string.
   *
   * @method encode
   * @param {Any} key
   *        The key to encode to a string.
   * @return {String}
   */
  encode: toJson,
  
  /**
   * Returns true if this Stork is not ready for storage calls and queues
   * the method and arguments to be called after this Stork is initialized.
   *
   * @private
   * @param  {function} method 
   *         The reference to the calling function
   * @param  {Arguments} args 
   *         The arguments of the calling function
   * @param  {Stork.Promise} promise 
   *         The promise to notify when the function is finally called.
   * @return {Boolean} -
   *         Returns true if the calling function should return this
   *         immediately because the implementation isn't initialized yet.
   */
  handlePending: function(method, args, promise) 
  {
    var handled = !this.initialized;

    if (handled) 
    {
      this.pending.push(
      {
        method: method,
        arguments: Array.prototype.slice.call( args ),
        promise: promise

      });

      if ( promise )
      {
        promise.$reset();
      }
    }

    return handled;
  },

  /**
   * Marks the Stork as initialized and executes any pending functions.
   *
   * @private
   * @param  {Stork.Promise} promise
   *         The promise for {@link Stork#init} or {@link Stork#reload}.
   * @return {Stork} -
   *         A reference to this.
   */
  finishInitialization: function(promise, args) 
  {
    if (!this.initialized) 
    {
      this.initialized = true;

      promise.$success( args );

      for (var i = 0; i < this.pending.length; i++) 
      {
        var pending = this.pending[ i ];
        var newPromise = pending.method.apply( this, pending.arguments );

        if ( pending.promise )
        {
          pending.promise.$bindTo( newPromise );
        }
      }

      this.pending = null;
    }

    return this;
  },

  /**
   * Finishes the reload function passing the now cached values and keys
   * to the success callbacks.
   *
   * @private
   * @param  {Stock.Promise} promise
   *         The promise for the {@link Stork#reload} invocation.
   */
  finishReload: function(promise)
  {
    if ( promise.$pending() )
    {
      var cache = this.cache;

      if ( this.initialized )
      {
        promise.$success( [cache.values, cache.okeys] );
      }
      else
      {
        this.finishInitialization( promise, [cache.values, cache.okeys] );
      }
    }
  },

  /**
   * Determines whether this Stork implementation is available.
   * 
   * @return {Boolean} True if this Stork is usable, otherwise false.
   */
  valid: function() 
  {
    throw 'Stork.valid is not implemented';
  },
  
  /**
   * The format of success callback for {@link Stork#init}.
   * 
   * @callback Stork~initSuccess
   * @param {Stork} stork
   *        The reference to this Stork instance.
   */

  /**
   * The format of failure callback for {@link Stork#init}.
   * 
   * @callback Stork~initFailure
   * @param {Any} error
   *        The error that was thrown.
   */

  /**
   * Initializes this Stork instance. If `options.lazy` is passed in as true,
   * key-value pairs will not be loaded here, otherwise all key-value
   * pairs will be loaded. This function is automatically called at the end
   * of the Stork constructor with the options passed to the constructor.
   * 
   * @param  {Object} options
   *         The initialization options.
   * @param  {Stork~initSuccess} [success]
   *         The function to invoke when the Stork instance successfully 
   *         initializes and is usable.
   * @param  {Stork~initFailure} [failure]
   *         The function to invoke if there's a problem initializing.
   * @return {Stork.Promise} -
   *         The promise that can be used to listen for success or failure, as
   *         well as chaining additional calls.
   */
  init: function(options, success, failure) 
  {
    throw 'Stork.init is not implemented';
  },
  
  /**
   * The format of success callback for {@link Stork#reload}.
   * 
   * @callback Stork~reloadSuccess
   * @param {Array} values
   *        An array of all values loaded. This should not be modified.
   * @param {Array} keys
   *        An array of all keys loaded. This should not be modified.
   */

  /**
   * The format of failure callback for {@link Stork#reload}.
   * 
   * @callback Stork~reloadFailure
   * @param {Any} error
   *        The error that was thrown.
   */

  /**
   * Loads all key-value pairs into the cache which will increase performance 
   * for fetching operations ({@link Stork#get}, {@link Stork#getMany}, 
   * {@link Stork#each}, {@link Stork#all}).
   *
   * *Usage*
   * ```javascript
   * var onSuccessFunc = function(values, keys) {
   *   // handle success
   * };
   * var onFailureFunc = function(error) {
   *   // uh oh!
   * };
   * db.reload(); // I don't care about whether it succeeds or fails
   * db.reload( onSucessFunc, onFailureFunc ); // listen for success/failure
   * db.reload().then( onSuccessFunc, onFailureFunc ); // listen to promise
   * ```
   * 
   * @param  {Stork~reloadSuccess} [success]
   *         The function to invoke when all key-value pairs are loaded.
   * @param  {Stork~reloadFailure} [failure]
   *         The function to invoke if there was a problem loading all key-value
   *         pairs.
   * @return {Stork.Promise} -
   *         The promise that can be used to listen for success or failure, as
   *         well as chaining additional calls.
   */
  reload: function(success, failure)
  {
    throw 'Stork.reload is not implemented';
  },

  /**
   * A helper method for creating a consistent look when chaining promised
   * functions.
   *
   * *Usage*
   * ```javascript
   * db.then(function() { // <--
   *     // this === db, how big is it?
   *     return this.size();
   *   })
   *   .then(function(size) {
   *     // size has been determined, destroy!
   *     return this.destroy();
   *   })
   *   .then(function(){
   *     // You sunk my battleship! (destroyed db)
   *   })
   * ;
   * ```
   * 
   * @param  {function} callback
   *         The callback to invoke with this Stork instance as `this`.
   * @return {Stork.Promise} -
   *         The callback should return a Promise to chain additional functions.
   */
  then: function(callback)
  {
    return callback.apply( this );
  },

  /**
   * The format of success callback for {@link Stork#getMany}.
   * 
   * @callback Stork~getManySuccess
   * @param {Array} values
   *        The array of values associated to the given keys. If a key wasn't 
   *        found then the value in the array will be `undefined`.
   */

  /**
   * The format of failure callback for {@link Stork#getMany}.
   * 
   * @callback Stork~getManyFailure
   * @param {Array} keys
   *        The keys given that resulted in an error.
   * @param {Any} error 
   *        The error that was thrown.
   */

  /**
   * Gets an array of values given an array of keys and returns it to the
   * callback. If the key doesn't exist then the corresponding value in the
   * returned array will be `undefined`.
   *
   * *Usage*
   * ```javascript
   * var onSuccessFunc = function(values, keys) {
   *   // handle success
   * };
   * var onFailureFunc = function(keys, error) {
   *   // uh oh!
   * };
   * db.getMany( arrayOfKeys, onSucessFunc, onFailureFunc ); // listen for success/failure
   * db.getMany( arrayOfKeys ).then( onSuccessFunc, onFailureFunc ); // listen to promise
   * ```
   * 
   * @param  {Array} keys
   *         The keys of the key-value pairs to get.
   * @param  {Stork~getManySuccess} [success]
   *         THe function to invoke with the values found.
   * @param  {Stork~getManyFailure} [failure]
   *         The function to invoke if there was a problem getting values.
   * @return {Stork.Promise} -
   *         The promise that can be used to listen for success or failure, as
   *         well as chaining additional calls.
   */
  getMany: function(keys, success, failure)
  {
    var promise = Promise.Group( keys.length, this, success, failure );

    if ( this.handlePending( this.getMany, arguments, promise ) ) 
    {
      return promise;
    }

    var values = [];

    var addValue = function(i)
    {
      return function(value)
      {
        values[ i ] = value;

        promise.$success( [values, keys] );
      };
    };
    var onFailure = function(e)
    {
      promise.$failure( [keys, e] );
    };

    for (var i = 0; i < keys.length; i++)
    {
      this.get( keys[ i ], addValue( i ), onFailure );
    }

    return promise;
  },

  /**
   * The format of success callback for {@link Stork#get}.
   * 
   * @callback Stork~getSuccess
   * @param {Any} value
   *        The value associated to the given key or `undefined` if one was not
   *        found.
   * @param {Any} key
   *        The key of the key-value pair that was successfully found.
   */

  /**
   * The format of failure callback for {@link Stork#get}.
   * 
   * @callback Stork~getFailure
   * @param {Any} key
   *        The key of the key-value pair that was unsuccessfully gotten.
   * @param {Any} error 
   *        The error that was thrown.
   */

  /**
   * Gets the value for the given key and returns it to the callback. If the
   * key doesn't exist then `undefined` is given to the callback.
   *
   * *Usage*
   * ```javascript
   * var onSuccessFunc = function(value, key) {
   *   // handle success
   * };
   * var onFailureFunc = function(key, error) {
   *   // uh oh!
   * };
   * db.get( key, onSucessFunc, onFailureFunc ); // listen for success/failure
   * db.get( key ).then( onSuccessFunc, onFailureFunc ); // listen to promise
   * ```
   * 
   * @param  {Any} key
   *         The key of the key-value pair to get.
   * @param  {Stork~getSuccess} [success]
   *         The function to invoke if a value is successfully found or not found.
   * @param  {Stork~getFailure} [failure]
   *         The function to invoke if there was a problem.
   * @return {Stork.Promise} -
   *         The promise that can be used to listen for success or failure, as
   *         well as chaining additional calls.
   */
  get: function (key, success, failure)
  {
    var promise = new Promise( this, success, failure );

    if ( this.handlePending( this.get, arguments, promise ) ) 
    {
      return promise;
    }

    var rawKey;

    try
    {
      rawKey = this.encode( key );
    }
    catch (e)
    {
      promise.$failure( [key, e] );
    }

    if ( promise.$pending() )
    {
      if ( this.cache.has( rawKey ) )
      {
        promise.$success( [this.cache.get( rawKey ), key] );
      }
      else if ( this.loaded )
      {
        promise.$success( [undefined, key] );
      }
      else
      {
        this._get( key, rawKey, promise );
      }
    }

    return promise;
  },

  _get: function(key, rawKey, promise)
  {
    throw 'Stork._get is not implemented';
  },

  /**
   * The format of success callback for {@link Stork#destroy}.
   * 
   * @callback Stork~destroySuccess
   */

  /**
   * The format of failure callback for {@link Stork#destroy}.
   * 
   * @callback Stork~destroyFailure
   * @param {Any} error 
   *        The error that was thrown.
   */

  /**
   * Removes all key-value pairs and invokes the callback.
   *
   * *Usage*
   * ```javascript
   * var onSuccessFunc = function() {
   *   // DESTROYED!
   * };
   * var onFailureFunc = function(error) {
   *   // uh oh!
   * };
   * db.destroy(); // I don't care about whether it succeeds or fails
   * db.destroy( onSucessFunc, onFailureFunc ); // listen for success/failure
   * db.destroy().then( onSuccessFunc, onFailureFunc ); // listen to promise
   * ```
   * 
   * @param  {Stork~destroySuccess} [success]
   *         The function invoked when all key-value pairs are removed.
   * @param  {Stork~destroyFailure} [failure]
   *         The function invoked if there was a problem removing all key-value
   *         pairs.
   * @return {Stork.Promise} -
   *         The promise that can be used to listen for success or failure, as
   *         well as chaining additional calls.
   */
  destroy: function(success, failure) 
  {
    var promise = new Promise( this, success, failure );

    if ( this.handlePending( this.destroy, arguments, promise ) ) 
    {
      return promise;
    }

    this._destroy( promise );

    return promise;
  },

  _destroy: function(promise)
  {
    throw 'Stork._destroy is not implemented';
  },

  /**
   * The format of success callback for {@link Stork#save}.
   * 
   * @callback Stork~saveSuccess
   * @param {Object} record
   *        The record that successfully saved.
   */

  /**
   * The format of failure callback for {@link Stork#save}.
   * 
   * @callback Stork~saveFailure
   * @param {Object} record
   *        The record that failed to save.
   * @param {Any} error 
   *        The error that was thrown.
   */

  /**
   * Saves an `Object` record and returns the saved record to the callback. The 
   * record is the value in the key-value pair and the key is pulled from the 
   * record based on the options passed into the {@link Stork#init} function. 
   * The property used as the key is `this.key` and by default is `id`. If a key 
   * isn't specified in a record then a UUID is used and placed in the object.
   *
   * *Usage*
   * ```javascript
   * var onSuccessFunc = function(record) {
   *   // handle success
   * };
   * var onFailureFunc = function(record, error) {
   *   // uh oh!
   * };
   * db.save( record ); // I don't care about whether it succeeds or fails
   * db.save( record, onSucessFunc, onFailureFunc ); // listen for success/failure
   * db.save( record ).then( onSuccessFunc, onFailureFunc ); // listen to promise
   * ```
   * 
   * @param  {Object} record
   *         The record to save.
   * @param  {Stork~saveSuccess} [success]
   *         The function to invoke when the record is successfully saved.
   * @param  {Stork~saveFailure} [failure]
   *         The function to invoke if the record fails to save.
   * @return {Stork.Promise} -
   *         The promise that can be used to listen for success or failure, as
   *         well as chaining additional calls.
   */
  save: function(record, success, failure)
  {
    var promise = new Promise( this, success, failure );

    if ( this.handlePending( this.save, arguments, promise ) ) 
    {
      return promise;
    }

    var keyName = this.key;
    var key = record[ keyName ];

    if ( undef( key ) ) 
    {
      key = record[ keyName ] = uuid();
    }

    var onSuccess = function(key, value)
    {
      promise.$success( [value] );
    };
    var onFailure = function(key, value, error)
    {
      promise.$failure( [value, error] );
    };

    this.put( key, record, onSuccess, onFailure );

    return promise;
  },

  /**
   * The format of success callback for {@link Stork#batch}.
   * 
   * @callback Stork~batchSuccess
   * @param {Array} records
   *        The records successfully saved.
   */

  /**
   * The format of failure callback for {@link Stork#batch}.
   * 
   * @callback Stork~batchFailure
   * @param {Array} records
   *        The records unsuccessfully saved.
   * @param {Number} recordsSaved
   *        The number of records that successfully saved.
   * @param {Any} error 
   *        The error that was thrown.
   */

  /**
   * Saves an array of `Object` records and returns the records saved to the 
   * callback. The record is the value in the key-value pair and the key is 
   * pulled from the record based on the options passed into the 
   * {@link Stork#init} function. The property used as the key is `this.key` and
   * by default is `id`. If a key isn't specified in a record then a UUID is 
   * used and placed in the object.
   *
   * *Usage*
   * ```javascript
   * var onSuccessFunc = function(records) {
   *   // handle success
   * };
   * var onFailureFunc = function(records, recordsSaved, error) {
   *   // uh oh!
   * };
   * db.batch( records ); // I don't care about whether it succeeds or fails
   * db.batch( records, onSucessFunc, onFailureFunc ); // listen for success/failure
   * db.batch( records ).then( onSuccessFunc, onFailureFunc ); // listen to promise
   * ```
   * 
   * @param  {Array} records
   *         The array of objects to save.
   * @param  {Stork~batchSuccess} [success]
   *         The function to invoke when all records are successfully saved.
   * @param  {Stork~batchFailure} [failure]
   *         The function to invoke if any of the records failed to save.
   * @return {Stork.Promise} -
   *         The promise that can be used to listen for success or failure, as
   *         well as chaining additional calls.
   */
  batch: function(records, success, failure)
  {
    var promise = Promise.Group( records.length, this, success, failure );

    if ( this.handlePending( this.batch, arguments, promise ) ) 
    {
      return promise;
    }

    var onSaved = function() 
    {
      promise.$success( [records] );
    };
    var setFailure = function(e) 
    {
      promise.$failure( [records, saves, e] );
    };

    for (var i = 0; i < records.length && !promise.state; i++)
    {
      this.save( records[ i ], onSaved, setFailure );
    }

    return promise;
  },

  /**
   * The format of success callback for {@link Stork#put}.
   * 
   * @callback Stork~putSuccess
   * @param {Any} key
   *        The key to add or update.
   * @param {Any} value
   *        The value to add or update.
   * @param {Any} previousValue
   *        The previous value for the key if it exists in the cache.
   */

  /**
   * The format of failure callback for {@link Stork#put}.
   * 
   * @callback Stork~putFailure
   * @param {Any} key
   *        The key that failed to be added or updated.
   * @param {Any} value
   *        The value that failed to be added or updated.
   * @param {Any} error 
   *        The error that was thrown.
   */

  /**
   * Adds or updates the value mapped by the given key and returns the key
   * and value placed to the callback.
   *
   * *Usage*
   * ```javascript
   * var onSuccessFunc = function(key, value, previousValue) {
   *   // handle success
   * };
   * var onFailureFunc = function(key, value, error) {
   *   // uh oh!
   * };
   * db.put( key, value ); // I don't care about whether it succeeds or fails
   * db.put( key, value, onSucessFunc, onFailureFunc ); // listen for success/failure
   * db.put( key, value ).then( onSuccessFunc, onFailureFunc ); // listen to promise
   * ```
   * 
   * @param  {Any} key
   *         The key to add or update.
   * @param  {Any} value
   *         The value to add or update.
   * @param  {Stork~putSuccess} [success]
   *         The function to invoke when the key-value pair is successfully 
   *         added or updated.
   * @param  {Stork~putFailure} [failure]
   *         The function to invoke if there was a problem putting the key-value
   *         pair.
   * @return {Stork.Promise} -
   *         The promise that can be used to listen for success or failure, as
   *         well as chaining additional calls.
   */
  put: function(key, value, success, failure)
  {
    var promise = new Promise( this, success, failure );

    if ( this.handlePending( this.put, arguments, promise ) ) 
    {
      return promise;
    }

    var rawKey, rawValue;

    try
    {
      rawKey = this.encode( key );
      rawValue = toJson( value );   
    }
    catch (e)
    {
      promise.$failure( [key, value, e] );
    }

    if ( promise.$pending() )
    {
      this._put( key, value, rawKey, rawValue, promise );
    }

    return promise;
  },

  _put: function(key, value, rawKey, rawValue, promise)
  {
    throw 'Stork._put is not implemented';
  },

  /**
   * The format of success callback for {@link Stork#remove}.
   * 
   * @callback Stork~removeSuccess
   * @param {Any} value
   *        The value removed or `undefined` if the key didn't exist.
   * @param {Any} key
   *        The key of the key-value pair that was removed.
   */

  /**
   * The format of failure callback for {@link Stork#remove}.
   * 
   * @callback Stork~removeFailure
   * @param {Any} key
   *        The key of the key-value pair that failed to be removed.
   * @param {Any} error 
   *        The error that was thrown.
   */

  /**
   * Removes the key-value pair for the given key and returns the removed value
   * to the callback if on existed.
   *
   * *Usage*
   * ```javascript
   * var onSuccessFunc = function(value, key) {
   *   // handle success
   * };
   * var onFailureFunc = function(key, error) {
   *   // uh oh!
   * };
   * db.remove( key ); // I don't care about whether it succeeds or fails
   * db.remove( key, onSucessFunc, onFailureFunc ); // listen for success/failure
   * db.remove( key ).then( onSuccessFunc, onFailureFunc ); // listen to promise
   * ```
   * 
   * @param  {Any} key
   *         The key of the key-value pair to remove.
   * @param  {Stork~removeSuccess} [success]
   *         The function to invoke then the key is removed or doesn't exist.
   * @param  {Stork~removeFailure} [failure]
   *         The function to invoke if there was a problem removing the key.
   * @return {Stork.Promise} -
   *         The promise that can be used to listen for success or failure, as
   *         well as chaining additional calls.
   */
  remove: function(key, success, failure)
  {
    var promise = new Promise( this, success, failure );

    if ( this.handlePending( this.remove, arguments, promise ) ) 
    {
      return promise;
    }

    var rawKey;

    try
    {
      rawKey = this.encode( key );
    }
    catch (e)
    {
      promise.$failure( [key, e] );
    }

    if ( promise.$pending() )
    {
      if ( this.loaded && !this.cache.has( rawKey ) )
      {
        promise.$success( [undefined, key] );
      }
      else
      {
        var value = this.cache.get( rawKey );

        this._remove( key, rawKey, value, promise );      
      }
    }

    return promise;
  },

  _remove: function(key, rawKey, value, promise)
  {
    throw 'Stork._remove is not implemented';
  },

  /**
   * The format of success callback for {@link Stork#removeMany}.
   * 
   * @callback Stork~removeManySuccess
   * @param {Array} values
   *        The values removed in the same order of the keys. If a key didn't
   *        exist then the corresponding value in the array will be `undefined`.
   * @param {Array} keys
   *        The corresponding removed keys.
   */

  /**
   * The format of failure callback for {@link Stork#removeMany}.
   * 
   * @callback Stork~removeManyFailure
   * @param {Array} values
   *        The values removed in the same order of the given keys.
   * @param {Number} removed
   *        The number of records removed before the error occurred.
   * @param {Any} error 
   *        The error that was thrown.
   */

  /**
   * Removes multiple key-value pairs and returns the values removed to the 
   * given callback.
   *
   * *Usage*
   * ```javascript
   * var onSuccessFunc = function(values, keys) {
   *   // handle success
   * };
   * var onFailureFunc = function(values, removed, error) {
   *   // uh oh!
   * };
   * db.removeMany( keys ); // I don't care about whether it succeeds or fails
   * db.removeMany( keys, onSucessFunc, onFailureFunc ); // listen for success/failure
   * db.removeMany( keys ).then( onSuccessFunc, onFailureFunc ); // listen to promise
   * ```
   * 
   * @param  {Array} keys
   *         The array of keys to remove.
   * @param  {Stork~removeManySuccess} [success]
   *         The function to invoke once all matching key-value pairs are 
   *         removed, with the values removed.
   * @param  {Stork~removeManyFailure} [failure]
   *         The function to invoke if there was a problem removing any of the
   *         key-value pairs.
   * @return {Stork.Promise} -
   *         The promise that can be used to listen for success or failure, as
   *         well as chaining additional calls.
   */
  removeMany: function(keys, success, failure)
  {
    var promise = Promise.Group( keys.length, this, success, failure );

    if ( this.handlePending( this.removeMany, arguments, promise ) ) 
    {
      return promise;
    }

    var values = [];
    var removed = 0;

    var addValue = function(i)
    {
      return function(value) 
      {
        values[ i ] = value;
        removed++;

        promise.$success( [values, keys] );
      };
    };
    var setFailure = function(e) 
    {
      promise.$failure( [values, removed, e] );
    };

    for (var i = 0; i < keys.length; i++)
    {
      this.remove( keys[ i ], addValue( i ), setFailure )
    }

    return promise;
  },

  /**
   * The format of success callback for {@link Stork#each}.
   * 
   * @callback Stork~eachSuccess
   * @param {Any} value
   *        The value of the current key-value pair.
   * @param {Any} key
   *        The key of the current key-value pair.
   */

  /**
   * The format of failure callback for {@link Stork#each}.
   * 
   * @callback Stork~eachFailure
   * @param {Any} error 
   *        The error that was thrown.
   */

  /**
   * Returns every key-value pair individually to the given callback.
   *
   * *Usage*
   * ```javascript
   * var onPairFunc = function(value, key) {
   *   // handle success
   * };
   * var onFailureFunc = function(error) {
   *   // uh oh!
   * };
   * db.each( onPairFunc ); // I don't care about whether it fails
   * db.each( onPairFunc, onFailureFunc ); // listen for success & failure
   * ```
   * 
   * @param  {Stork~eachSuccess} callback
   *         The function to invoke for each key-value pair.
   * @param  {Stork~eachFailure} [failure]
   *         The function to invoke if there was a problem iterating the 
   *         key-value pairs.
   * @return {Stork} -
   *         The reference to this Stork instance.
   */
  each: function(callback, failure)
  {
    if ( !isFunc( callback ) || this.handlePending( this.each, arguments ) ) 
    {
      return this;
    }

    var stork = this;
    var iterate = function(values, keys)
    {
      for (var i = 0; i < values.length; i++)
      {
        callback.call( stork, values[ i ], keys[ i ] );
      }
    };

    if ( this.loaded )
    {
      var keys = this.cache.okeys;
      var values = this.cache.values;

      iterate( values, keys );
    }
    else
    {
      this.reload( iterate, failure );
    }

    return this;
  },

  /**
   * The format of success callback for {@link Stork#size}.
   * 
   * @callback Stork~sizeSuccess
   * @param {Number} count
   *        The total number of key-value pairs.
   */

  /**
   * The format of failure callback for {@link Stork#size}.
   * 
   * @callback Stork~sizeFailure
   * @param {Any} error 
   *        The error that was thrown.
   */

  /**
   * Returns the number of key-value pairs to the success callback.
   *
   * *Usage*
   * ```javascript
   * var onSuccessFunc = function(count) {
   *   // handle success
   * };
   * var onFailureFunc = function(error) {
   *   // uh oh!
   * };
   * db.size( onSucessFunc, onFailureFunc ); // listen for success/failure
   * db.size().then( onSuccessFunc, onFailureFunc ); // listen to promise
   * ```
   * 
   * @param  {Stork~sizeSuccess} [success]
   *         The function to invoke with the number of key-value pairs.
   * @param  {Stork~sizeFailure} [failure]
   *         The function to invoke if there was a problem determining the
   *         number of key-value pairs.
   * @return {Stork.Promise} -
   *         The promise that can be used to listen for success or failure, as
   *         well as chaining additional calls.
   */
  size: function(success, failure)
  {
    var promise = new Promise( this, success, failure );

    if ( this.handlePending( this.size, arguments, promise ) ) 
    {
      return promise;
    }

    if ( this.loaded )
    {
      promise.$success( [this.cache.size()] );
    }
    else
    {
      this._size( promise );
    }

    return promise;
  },

  _size: function(promise)
  {
    throw 'Stork._size is not implemented';
  },
  
  /**
   * The format of success callback for {@link Stork#all}.
   * 
   * @callback Stork~allSuccess
   * @param {Array} values
   *        An array of all values stored. This should not be modified.
   * @param {Array} keys
   *        An array of all keys stored. This should not be modified.
   */

  /**
   * The format of failure callback for {@link Stork#all}.
   * 
   * @callback Stork~allFailure
   * @param {Any} error
   *        The error that was thrown.
   */

  /**
   * Returns all key-value pairs to the success callback.
   *
   * *Usage*
   * ```javascript
   * var onSuccessFunc = function(values, keys) {
   *   // handle success
   * };
   * var onFailureFunc = function(error) {
   *   // uh oh!
   * };
   * db.all( onSucessFunc, onFailureFunc ); // listen for success/failure
   * db.all().then( onSuccessFunc, onFailureFunc ); // listen to promise
   * ```
   * 
   * @param  {Stork~allSuccess} [success]
   *         The function to invoke with all the key-value pairs.
   * @param  {Stork~allFailure} [failure]
   *         The function to invoke if this Stork was unable to return all of the key-value pairs.
   * @return {Stork.Promise} -
   *         The promise that can be used to listen for success or failure, as
   *         well as chaining additional calls.
   */
  all: function(success, failure)
  {
    var promise = new Promise( this, success, failure );

    if ( this.handlePending( this.all, arguments, promise ) ) 
    {
      return promise;
    }

    var returnAll = function(values, keys)
    {
      promise.$success( [values, keys] );
    };
    var onFailure = function(error)
    {
      promise.$failure( [error] );
    };

    if ( this.loaded )
    {
      var keys = this.cache.okeys;
      var values = this.cache.values;

      returnAll( values, keys );
    }
    else
    {
      this.reload( returnAll, onFailure );
    }

    return promise;
  }

};