Full Code of tantaman/LargeLocalStorage for AI

master e4fc5d03be1d cached
33 files
510.1 KB
130.4k tokens
270 symbols
1 requests
Download .txt
Showing preview only (528K chars total). Download the full file or copy to clipboard to get everything.
Repository: tantaman/LargeLocalStorage
Branch: master
Commit: e4fc5d03be1d
Files: 33
Total size: 510.1 KB

Directory structure:
gitextract_1j73emsu/

├── .gitignore
├── Gruntfile.js
├── LICENSE-MIT.txt
├── README.md
├── bower.json
├── dist/
│   ├── LargeLocalStorage.js
│   └── contrib/
│       ├── S3Link.js
│       └── URLCache.js
├── examples/
│   └── album/
│       ├── app.js
│       ├── index.html
│       └── main.css
├── package.json
├── src/
│   ├── LargeLocalStorage.js
│   ├── contrib/
│   │   ├── S3Link.js
│   │   └── URLCache.js
│   ├── errors.js
│   ├── footer.js
│   ├── header.js
│   ├── impls/
│   │   ├── FilesystemAPIProvider.js
│   │   ├── IndexedDBProvider.js
│   │   ├── LocalStorageProvider.js
│   │   ├── WebSQLProvider.js
│   │   └── utils.js
│   └── pipeline.js
└── test/
    ├── index.html
    ├── lib/
    │   ├── chai.js
    │   ├── expect.js
    │   ├── mocha/
    │   │   ├── mocha.css
    │   │   └── mocha.js
    │   └── sinon.js
    ├── runner/
    │   └── mocha.js
    └── spec/
        ├── LargeLocalStorageTest.js
        └── URLCacheTest.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
bower_components/
node_modules/
doc/
temp.out


================================================
FILE: Gruntfile.js
================================================
'use strict';

module.exports = function (grunt) {
	require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);

	grunt.initConfig({
		pkg: grunt.file.readJSON('package.json'),
		concat: {
			options: {
				seperator: ';'
			},
			scripts: {
				src: ['src/header.js',
					  'src/pipeline.js',
					  'src/impls/utils.js',
					  'src/impls/FilesystemAPIProvider.js',
					  'src/impls/IndexedDBProvider.js',
					  'src/impls/LocalStorageProvider.js',
					  'src/impls/WebSQLProvider.js',
					  'src/LargeLocalStorage.js',
					  'src/footer.js'],
				dest: 'dist/LargeLocalStorage.js'
			}
		},

		uglify: {
			options: {
				mangle: {
					except: ['Q']
				}
			},
			scripts: {
				files: {
					'dist/LargeLocalStorage.min.js': ['dist/LargeLocalStorage.js']
				}
			}
		},

		watch: {
			scripts: {
				files: ["src/**/*.js"],
				tasks: ["concat"]
			},
			contrib: {
				files: ["src/contrib/**/*.js"],
				tasks: ["copy:contrib"]
			}
		},

		copy: {
			contrib: {
				files: [{expand: true, cwd: "src/contrib/", src: "**", dest: "dist/contrib/"}]
			}
		},

		connect: {
			server: {
				options: {
					port: 9001,
					base: '.'
				}
			}
		},

		// docview: {
		// 	compile: {
		// 		files: {
		// 			"doc/LargeLocalStorage.html": "doc/library.handlebars"
		// 		}
		// 	}
		// },

		yuidoc: {
			compile: {
				name: '<%= pkg.name %>',
				description: '<%= pkg.description %>',
				version: '<%= pkg.version %>',
				url: '<%= pkg.homepage %>',
				options: {
					paths: 'src',
					themedir: 'node_modules/yuidoc-library-theme',
					helpers: ['node_modules/yuidoc-library-theme/helpers/helpers.js'],
					outdir: 'doc',
					// parseOnly: true
			      }
			}
		}
	});

	grunt.registerTask('default', ['concat', 'copy', 'connect', 'watch']);
	grunt.registerTask('docs', ['yuidoc']);
	grunt.registerTask('build', ['concat', 'copy', 'uglify']);
};


================================================
FILE: LICENSE-MIT.txt
================================================
Copyright (c) 2013 Matt Crinklaw-Vogt

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.


================================================
FILE: README.md
================================================
LargeLocalStorage
=================


**Problem:** You need a large key-value store in the browser.

To make things worse: 
* DOMStorage only gives you 5mb
* Chrome doesn't let you store blobs in IndexedDB
* Safari doesn't support IndexedDB,
* IE and Firefox both support IndexedDB but not the FilesystemAPI.

`LargeLocalStorage` bridges all of that to give you a large capacity (up to several GB when authorized by the user) key-value store in the browser
(IE 10, Chrome, Safari 6+, Firefox, Opera). 

* [docs](http://tantaman.github.io/LargeLocalStorage/doc/classes/LargeLocalStorage.html)
* [tests](http://tantaman.github.io/LargeLocalStorage/test/)
* [demo app](http://tantaman.github.io/LargeLocalStorage/examples/album/)

## Basic Rundown / Examples

### Creating a database

```javascript
// Specify desired capacity in bytes
var desiredCapacity = 125 * 1024 * 1024;

// Create a 125MB key-value store
var storage = new LargeLocalStorage({size: desiredCapacity, name: 'myDb'});

// Await initialization of the storage area
storage.initialized.then(function(grantedCapacity) {
  // Check to see how much space the user authorized us to actually use.
  // Some browsers don't indicate how much space was granted in which case
  // grantedCapacity will be 1.
  if (grantedCapacity != -1 && grantedCapacity != desiredCapacity) {
  }
});
```
  
### Setting data

```javascript  
// You can set the contents of "documents" which are identified by a key.
// Documents can only contains strings for their values but binary
// data can be added as attachments.
// All operations are asynchronous and return Q promises
storage.setContents('docKey', "the contents...").then(function() {
  alert('doc created/updated');
});
  
// Attachments can be added to documents.
// Attachments are Blobs or any subclass of Blob (e.g, File).
// Attachments can be added whether or not a corresponding document exists.
// setAttachment returns a promise so you know when the set has completed.
storage.setAttachment('myDoc', 'titleImage', blob).then(function() {
    alert('finished setting the titleImage attachment');
});
```

### Retrieving Data

```javascript
// get the contents of a document
storage.getContents('myDoc').then(function(content) {
});

// Call getAttachment with the docKey and attachmentKey
storage.getAttachment('myDoc', 'titleImage').then(function(titleImage) {
    // Create an image element with the retrieved attachment
    // (or video or sound or whatever you decide to attach and use)
    var img = new Image();
    img.src = URL.createObjectURL(titleImage);
    document.body.appendChild(img);
    URL.revokeObjectURL(titleImage);
});


// If you just need a URL to your attachment you can get
// the attachment URL instead of the attachment itself
storge.getAttachmentURL('somePreviouslySavedDoc', 'someAttachment').then(function(url) {
  // do something with the attachment URL
  // ...
    
  // revoke the URL
  storage.revokeAttachmentURL(url);
});
```

### Listing
```javascript
// You can do an ls to get all of the keys in your data store
storage.ls().then(function(listing) {
  // listing is a list of all of the document keys
  alert(listing);
});
  
// Or get a listing of a document's attachments
storage.ls('somePreviouslySavedDoc').then(function(listing) {
  // listing is a list of all attachments belonging to `somePreviouslySavedDoc`
  alert(listing);
});
```

### Removing
```javascript
// you can remove a document with rm
// removing a document also removes all of that document's
// attachments.
storage.rm('somePreviouslySavedDoc');
  
// you can also rm an attachment
storage.rmAttachment('someOtherDocKey', 'attachmentKey');

// removals return promises as well so you know when the removal completes (or fails).
storage.rm('docKey').then(function() {
  alert('Removed!');
}, function(err) {
  console.error('Failed removal');
  console.error(err);
});

// clear the entire database
storage.clear();
```

More:
* Read the [docs](http://tantaman.github.io/LargeLocalStorage/doc/classes/LargeLocalStorage.html)
* Run the [tests](http://tantaman.github.io/LargeLocalStorage/test/)
* View the [demo app](http://tantaman.github.io/LargeLocalStorage/examples/album/)

##Including

Include it on your page with a script tag:

```
<script src="path/to/LargeLocalStorage.js"></script>
```

Or load it as an amd module:

```
define(['components/lls/dist/LargeLocalStorage'], function(lls) {
  var storage = new lls({size: 100 * 1024 * 1024});
});
```

LLS depends on [Q](https://github.com/kriskowal/q) so you'll have to make sure you have that dependency.

##Getting
downlad it directly

* (dev) https://raw.github.com/tantaman/LargeLocalStorage/master/dist/LargeLocalStorage.js
* (min) https://raw.github.com/tantaman/LargeLocalStorage/master/dist/LargeLocalStorage.min.js

Or `bower install lls`


================================================
FILE: bower.json
================================================
{
	"name": "lls",
	"version": "0.1.3",
	"main": "dist/LargeLocalStorage.js",
	"ignore": [
		"examples",
		"src",
		"test",
		"Gruntfile.js",
		"todo.txt",
		"package.json"
	],

	"dependencies": {
		"q": "~0.9.7"
	},

	"devDependencies": {
		"jquery": "~2.0.3",
		"bootstrap": "~3.0.0"
	}
}


================================================
FILE: dist/LargeLocalStorage.js
================================================
(function(glob) {
	var undefined = {}.a;

	function definition(Q) {
	

/**
@author Matt Crinklaw-Vogt
*/
function PipeContext(handlers, nextMehod, end) {
	this._handlers = handlers;
	this._next = nextMehod;
	this._end = end;

	this._i = 0;
}

PipeContext.prototype = {
	next: function() {
		// var args = Array.prototype.slice.call(arguments, 0);
		// args.unshift(this);
		this.__pipectx = this;
		return this._next.apply(this, arguments);
	},

	_nextHandler: function() {
		if (this._i >= this._handlers.length) return this._end;

		var handler = this._handlers[this._i].handler;
		this._i += 1;
		return handler;
	},

	length: function() {
		return this._handlers.length;
	}
};

function indexOfHandler(handlers, len, target) {
	for (var i = 0; i < len; ++i) {
		var handler = handlers[i];
		if (handler.name === target || handler.handler === target) {
			return i;
		}
	}

	return -1;
}

function forward(ctx) {
	return ctx.next.apply(ctx, Array.prototype.slice.call(arguments, 1));
}

function coerce(methodNames, handler) {
	methodNames.forEach(function(meth) {
		if (!handler[meth])
			handler[meth] = forward;
	});
}

var abstractPipeline = {
	addFirst: function(name, handler) {
		coerce(this._pipedMethodNames, handler);
		this._handlers.unshift({name: name, handler: handler});
	},

	addLast: function(name, handler) {
		coerce(this._pipedMethodNames, handler);
		this._handlers.push({name: name, handler: handler});
	},

 	/**
 	Add the handler with the given name after the 
 	handler specified by target.  Target can be a handler
 	name or a handler instance.
 	*/
	addAfter: function(target, name, handler) {
		coerce(this._pipedMethodNames, handler);
		var handlers = this._handlers;
		var len = handlers.length;
		var i = indexOfHandler(handlers, len, target);

		if (i >= 0) {
			handlers.splice(i+1, 0, {name: name, handler: handler});
		}
	},

	/**
	Add the handler with the given name after the handler
	specified by target.  Target can be a handler name or
	a handler instance.
	*/
	addBefore: function(target, name, handler) {
		coerce(this._pipedMethodNames, handler);
		var handlers = this._handlers;
		var len = handlers.length;
		var i = indexOfHandler(handlers, len, target);

		if (i >= 0) {
			handlers.splice(i, 0, {name: name, handler: handler});
		}
	},

	/**
	Replace the handler specified by target.
	*/
	replace: function(target, newName, handler) {
		coerce(this._pipedMethodNames, handler);
		var handlers = this._handlers;
		var len = handlers.length;
		var i = indexOfHandler(handlers, len, target);

		if (i >= 0) {
			handlers.splice(i, 1, {name: newName, handler: handler});
		}
	},

	removeFirst: function() {
		return this._handlers.shift();
	},

	removeLast: function() {
		return this._handlers.pop();
	},

	remove: function(target) {
		var handlers = this._handlers;
		var len = handlers.length;
		var i = indexOfHandler(handlers, len, target);

		if (i >= 0)
			handlers.splice(i, 1);
	},

	getHandler: function(name) {
		var i = indexOfHandler(this._handlers, this._handlers.length, name);
		if (i >= 0)
			return this._handlers[i].handler;
		return null;
	}
};

function createPipeline(pipedMethodNames) {
	var end = {};
	var endStubFunc = function() { return end; };
	var nextMethods = {};

	function Pipeline(pipedMethodNames) {
		this.pipe = {
			_handlers: [],
			_contextCtor: PipeContext,
			_nextMethods: nextMethods,
			end: end,
			_pipedMethodNames: pipedMethodNames
		};
	}

	var pipeline = new Pipeline(pipedMethodNames);
	for (var k in abstractPipeline) {
		pipeline.pipe[k] = abstractPipeline[k];
	}

	pipedMethodNames.forEach(function(name) {
		end[name] = endStubFunc;

		nextMethods[name] = new Function(
			"var handler = this._nextHandler();" +
			"handler.__pipectx = this.__pipectx;" +
			"return handler." + name + ".apply(handler, arguments);");

		pipeline[name] = new Function(
			"var ctx = new this.pipe._contextCtor(this.pipe._handlers, this.pipe._nextMethods." + name + ", this.pipe.end);"
			+ "return ctx.next.apply(ctx, arguments);");
	});

	return pipeline;
}

createPipeline.isPipeline = function(obj) {
	return obj instanceof Pipeline;
}
var utils = (function() {
	return {
		convertToBase64: function(blob, cb) {
			var fr = new FileReader();
			fr.onload = function(e) {
				cb(e.target.result);
			};
			fr.onerror = function(e) {
			};
			fr.onabort = function(e) {
			};
			fr.readAsDataURL(blob);
		},

		dataURLToBlob: function(dataURL) {
				var BASE64_MARKER = ';base64,';
				if (dataURL.indexOf(BASE64_MARKER) == -1) {
					var parts = dataURL.split(',');
					var contentType = parts[0].split(':')[1];
					var raw = parts[1];

					return new Blob([raw], {type: contentType});
				}

				var parts = dataURL.split(BASE64_MARKER);
				var contentType = parts[0].split(':')[1];
				var raw = window.atob(parts[1]);
				var rawLength = raw.length;

				var uInt8Array = new Uint8Array(rawLength);

				for (var i = 0; i < rawLength; ++i) {
					uInt8Array[i] = raw.charCodeAt(i);
				}

				return new Blob([uInt8Array.buffer], {type: contentType});
		},

		splitAttachmentPath: function(path) {
			var parts = path.split('/');
			if (parts.length == 1) 
				parts.unshift('__nodoc__');
			return parts;
		},

		mapAsync: function(fn, promise) {
			var deferred = Q.defer();
			promise.then(function(data) {
				_mapAsync(fn, data, [], deferred);
			}, function(e) {
				deferred.reject(e);
			});

			return deferred.promise;
		},

		countdown: function(n, cb) {
		    var args = [];
		    return function() {
		      for (var i = 0; i < arguments.length; ++i)
		        args.push(arguments[i]);
		      n -= 1;
		      if (n == 0)
		        cb.apply(this, args);
		    }
		}
	};

	function _mapAsync(fn, data, result, deferred) {
		fn(data[result.length], function(v) {
			result.push(v);
			if (result.length == data.length)
				deferred.resolve(result);
			else
				_mapAsync(fn, data, result, deferred);
		}, function(err) {
			deferred.reject(err);
		})
	}
})();
var requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
var persistentStorage = navigator.persistentStorage || navigator.webkitPersistentStorage;
var FilesystemAPIProvider = (function(Q) {
	function makeErrorHandler(deferred, finalDeferred) {
		// TODO: normalize the error so
		// we can handle it upstream
		return function(e) {
			if (e.code == 1) {
				deferred.resolve(undefined);
			} else {
				if (finalDeferred)
					finalDeferred.reject(e);
				else
					deferred.reject(e);
			}
		}
	}

	function getAttachmentPath(docKey, attachKey) {
		docKey = docKey.replace(/\//g, '--');
		var attachmentsDir = docKey + "-attachments";
		return {
			dir: attachmentsDir,
			path: attachmentsDir + "/" + attachKey
		};
	}

	function readDirEntries(reader, result) {
		var deferred = Q.defer();

		_readDirEntries(reader, result, deferred);

		return deferred.promise;
	}

	function _readDirEntries(reader, result, deferred) {
		reader.readEntries(function(entries) {
			if (entries.length == 0) {
				deferred.resolve(result);
			} else {
				result = result.concat(entries);
				_readDirEntries(reader, result, deferred);
			}
		}, function(err) {
			deferred.reject(err);
		});
	}

	function entryToFile(entry, cb, eb) {
		entry.file(cb, eb);
	}

	function entryToURL(entry) {
		return entry.toURL();
	}

	function FSAPI(fs, numBytes, prefix) {
		this._fs = fs;
		this._capacity = numBytes;
		this._prefix = prefix;
		this.type = "FileSystemAPI";
	}

	FSAPI.prototype = {
		getContents: function(path, options) {
			var deferred = Q.defer();
			path = this._prefix + path;
			this._fs.root.getFile(path, {}, function(fileEntry) {
				fileEntry.file(function(file) {
					var reader = new FileReader();

					reader.onloadend = function(e) {
						var data = e.target.result;
						var err;
						if (options && options.json) {
							try {
								data = JSON.parse(data);
							} catch(e) {
								err = new Error('unable to parse JSON for ' + path);
							}
						}

						if (err) {
							deferred.reject(err);
						} else {
							deferred.resolve(data);
						}
					};

					reader.readAsText(file);
				}, makeErrorHandler(deferred));
			}, makeErrorHandler(deferred));

			return deferred.promise;
		},

		// create a file at path
		// and write `data` to it
		setContents: function(path, data, options) {
			var deferred = Q.defer();

			if (options && options.json)
				data = JSON.stringify(data);

			path = this._prefix + path;
			this._fs.root.getFile(path, {create:true}, function(fileEntry) {
				fileEntry.createWriter(function(fileWriter) {
					var blob;
					fileWriter.onwriteend = function(e) {
						fileWriter.onwriteend = function() {
							deferred.resolve();
						};
						fileWriter.truncate(blob.size);
					}

					fileWriter.onerror = makeErrorHandler(deferred);

					if (data instanceof Blob) {
						blob = data;
					} else {
						blob = new Blob([data], {type: 'text/plain'});
					}

					fileWriter.write(blob);
				}, makeErrorHandler(deferred));
			}, makeErrorHandler(deferred));

			return deferred.promise;
		},

		ls: function(docKey) {
			var isRoot = false;
			if (!docKey) {docKey = this._prefix; isRoot = true;}
			else docKey = this._prefix + docKey + "-attachments";

			var deferred = Q.defer();

			this._fs.root.getDirectory(docKey, {create:false},
			function(entry) {
				var reader = entry.createReader();
				readDirEntries(reader, []).then(function(entries) {
					var listing = [];
					entries.forEach(function(entry) {
						if (!entry.isDirectory) {
							listing.push(entry.name);
						}
					});
					deferred.resolve(listing);
				});
			}, function(error) {
				deferred.reject(error);
			});

			return deferred.promise;
		},

		clear: function() {
			var deferred = Q.defer();
			var failed = false;
			var ecb = function(err) {
				failed = true;
				deferred.reject(err);
			}

			this._fs.root.getDirectory(this._prefix, {},
			function(entry) {
				var reader = entry.createReader();
				reader.readEntries(function(entries) {
					var latch = 
					utils.countdown(entries.length, function() {
						if (!failed)
							deferred.resolve();
					});

					entries.forEach(function(entry) {
						if (entry.isDirectory) {
							entry.removeRecursively(latch, ecb);
						} else {
							entry.remove(latch, ecb);
						}
					});

					if (entries.length == 0)
						deferred.resolve();
				}, ecb);
			}, ecb);

			return deferred.promise;
		},

		rm: function(path) {
			var deferred = Q.defer();
			var finalDeferred = Q.defer();

			// remove attachments that go along with the path
			path = this._prefix + path;
			var attachmentsDir = path + "-attachments";

			this._fs.root.getFile(path, {create:false},
				function(entry) {
					entry.remove(function() {
						deferred.promise.then(finalDeferred.resolve);
					}, function(err) {
						finalDeferred.reject(err);
					});
				},
				makeErrorHandler(finalDeferred));

			this._fs.root.getDirectory(attachmentsDir, {},
				function(entry) {
					entry.removeRecursively(function() {
						deferred.resolve();
					}, function(err) {
						finalDeferred.reject(err);
					});
				},
				makeErrorHandler(deferred, finalDeferred));

			return finalDeferred.promise;
		},

		getAttachment: function(docKey, attachKey) {
			var attachmentPath = this._prefix + getAttachmentPath(docKey, attachKey).path;

			var deferred = Q.defer();
			this._fs.root.getFile(attachmentPath, {}, function(fileEntry) {
				fileEntry.file(function(file) {
					if (file.size == 0)
						deferred.resolve(undefined);
					else
						deferred.resolve(file);
				}, makeErrorHandler(deferred));
			}, function(err) {
				if (err.code == 1) {
					deferred.resolve(undefined);
				} else {
					deferred.reject(err);
				}
			});

			return deferred.promise;
		},

		getAttachmentURL: function(docKey, attachKey) {
			var attachmentPath = this._prefix + getAttachmentPath(docKey, attachKey).path;

			var deferred = Q.defer();
			var url = 'filesystem:' + window.location.protocol + '//' + window.location.host + '/persistent/' + attachmentPath;
			deferred.resolve(url);
			// this._fs.root.getFile(attachmentPath, {}, function(fileEntry) {
			// 	deferred.resolve(fileEntry.toURL());
			// }, makeErrorHandler(deferred, "getting attachment file entry"));

			return deferred.promise;
		},

		getAllAttachments: function(docKey) {
			var deferred = Q.defer();
			var attachmentsDir = this._prefix + docKey + "-attachments";

			this._fs.root.getDirectory(attachmentsDir, {},
			function(entry) {
				var reader = entry.createReader();
				deferred.resolve(
					utils.mapAsync(function(entry, cb, eb) {
						entry.file(function(file) {
							cb({
								data: file,
								docKey: docKey,
								attachKey: entry.name
							});
						}, eb);
					}, readDirEntries(reader, [])));
			}, function(err) {
				deferred.resolve([]);
			});

			return deferred.promise;
		},

		getAllAttachmentURLs: function(docKey) {
			var deferred = Q.defer();
			var attachmentsDir = this._prefix + docKey + "-attachments";

			this._fs.root.getDirectory(attachmentsDir, {},
			function(entry) {
				var reader = entry.createReader();
				readDirEntries(reader, []).then(function(entries) {
					deferred.resolve(entries.map(
					function(entry) {
						return {
							url: entry.toURL(),
							docKey: docKey,
							attachKey: entry.name
						};
					}));
				});
			}, function(err) {
				deferred.reject(err);
			});

			return deferred.promise;
		},

		revokeAttachmentURL: function(url) {
			// we return FS urls so this is a no-op
			// unless someone is being silly and doing
			// createObjectURL(getAttachment()) ......
		},

		// Create a folder at dirname(path)+"-attachments"
		// add attachment under that folder as basename(path)
		setAttachment: function(docKey, attachKey, data) {
			var attachInfo = getAttachmentPath(docKey, attachKey);

			var deferred = Q.defer();

			var self = this;
			this._fs.root.getDirectory(this._prefix + attachInfo.dir,
			{create:true}, function(dirEntry) {
				deferred.resolve(self.setContents(attachInfo.path, data));
			}, makeErrorHandler(deferred));

			return deferred.promise;
		},

		// rm the thing at dirname(path)+"-attachments/"+basename(path)
		rmAttachment: function(docKey, attachKey) {
			var attachmentPath = getAttachmentPath(docKey, attachKey).path;

			var deferred = Q.defer();
			this._fs.root.getFile(this._prefix + attachmentPath, {create:false},
				function(entry) {
					entry.remove(function() {
						deferred.resolve();
					}, makeErrorHandler(deferred));
			}, makeErrorHandler(deferred));

			return deferred.promise;
		},

		getCapacity: function() {
			return this._capacity;
		}
	};

	return {
		init: function(config) {
			var deferred = Q.defer();

			if (!requestFileSystem) {
				deferred.reject("No FS API");
				return deferred.promise;
			}

			var prefix = config.name + '/';

			persistentStorage.requestQuota(config.size,
			function(numBytes) {
				requestFileSystem(window.PERSISTENT, numBytes,
				function(fs) {
					fs.root.getDirectory(config.name, {create: true},
					function() {
						deferred.resolve(new FSAPI(fs, numBytes, prefix));
					}, function(err) {
						console.error(err);
						deferred.reject(err);
					});
				}, function(err) {
					// TODO: implement various error messages.
					console.error(err);
					deferred.reject(err);
				});
			}, function(err) {
				// TODO: implement various error messages.
				console.error(err);
				deferred.reject(err);
			});

			return deferred.promise;
		},

		isAvailable: function() {
			return requestFileSystem != null;
		}
	}
})(Q);
var indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.OIndexedDB || window.msIndexedDB;
var IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.OIDBTransaction || window.msIDBTransaction;
var IndexedDBProvider = (function(Q) {
	var URL = window.URL || window.webkitURL;

	var convertToBase64 = utils.convertToBase64;
	var dataURLToBlob = utils.dataURLToBlob;

	function IDB(db) {
		this._db = db;
		this.type = 'IndexedDB';

		var transaction = this._db.transaction(['attachments'], 'readwrite');
		this._supportsBlobs = true;
		try {
			transaction.objectStore('attachments')
			.put(Blob(["sdf"], {type: "text/plain"}), "featurecheck");
		} catch (e) {
			this._supportsBlobs = false;
		}
	}

	// TODO: normalize returns and errors.
	IDB.prototype = {
		getContents: function(docKey) {
			var deferred = Q.defer();
			var transaction = this._db.transaction(['files'], 'readonly');

			var get = transaction.objectStore('files').get(docKey);
			get.onsuccess = function(e) {
				deferred.resolve(e.target.result);
			};

			get.onerror = function(e) {
				deferred.reject(e);
			};

			return deferred.promise;
		},

		setContents: function(docKey, data) {
			var deferred = Q.defer();
			var transaction = this._db.transaction(['files'], 'readwrite');

			var put = transaction.objectStore('files').put(data, docKey);
			put.onsuccess = function(e) {
				deferred.resolve(e);
			};

			put.onerror = function(e) {
				deferred.reject(e);
			};

			return deferred.promise;
		},

		rm: function(docKey) {
			var deferred = Q.defer();
			var finalDeferred = Q.defer();

			var transaction = this._db.transaction(['files', 'attachments'], 'readwrite');
			
			var del = transaction.objectStore('files').delete(docKey);

			del.onsuccess = function(e) {
				deferred.promise.then(function() {
					finalDeferred.resolve();
				});
			};

			del.onerror = function(e) {
				deferred.promise.catch(function() {
					finalDeferred.reject(e);
				});
			};

			var attachmentsStore = transaction.objectStore('attachments');
			var index = attachmentsStore.index('fname');
			var cursor = index.openCursor(IDBKeyRange.only(docKey));
			cursor.onsuccess = function(e) {
				var cursor = e.target.result;
				if (cursor) {
					cursor.delete();
					cursor.continue();
				} else {
					deferred.resolve();
				}
			};

			cursor.onerror = function(e) {
				deferred.reject(e);
			}

			return finalDeferred.promise;
		},

		getAttachment: function(docKey, attachKey) {
			var deferred = Q.defer();

			var transaction = this._db.transaction(['attachments'], 'readonly');
			var get = transaction.objectStore('attachments').get(docKey + '/' + attachKey);

			var self = this;
			get.onsuccess = function(e) {
				if (!e.target.result) {
					deferred.resolve(undefined);
					return;
				}

				var data = e.target.result.data;
				if (!self._supportsBlobs) {
					data = dataURLToBlob(data);
				}
				deferred.resolve(data);
			};

			get.onerror = function(e) {
				deferred.reject(e);
			};

			return deferred.promise;
		},

		ls: function(docKey) {
			var deferred = Q.defer();

			if (!docKey) {
				// list docs
				var store = 'files';
			} else {
				// list attachments
				var store = 'attachments';
			}

			var transaction = this._db.transaction([store], 'readonly');
			var cursor = transaction.objectStore(store).openCursor();
			var listing = [];

			cursor.onsuccess = function(e) {
				var cursor = e.target.result;
				if (cursor) {
					listing.push(!docKey ? cursor.key : cursor.key.split('/')[1]);
					cursor.continue();
				} else {
					deferred.resolve(listing);
				}
			};

			cursor.onerror = function(e) {
				deferred.reject(e);
			};

			return deferred.promise;
		},

		clear: function() {
			var deferred = Q.defer();
			var finalDeferred = Q.defer();

			var t = this._db.transaction(['attachments', 'files'], 'readwrite');


			var req1 = t.objectStore('attachments').clear();
			var req2 = t.objectStore('files').clear();

			req1.onsuccess = function() {
				deferred.promise.then(finalDeferred.resolve);
			};

			req2.onsuccess = function() {
				deferred.resolve();
			};

			req1.onerror = function(err) {
				finalDeferred.reject(err);
			};

			req2.onerror = function(err) {
				finalDeferred.reject(err);
			};

			return finalDeferred.promise;
		},

		getAllAttachments: function(docKey) {
			var deferred = Q.defer();
			var self = this;

			var transaction = this._db.transaction(['attachments'], 'readonly');
			var index = transaction.objectStore('attachments').index('fname');

			var cursor = index.openCursor(IDBKeyRange.only(docKey));
			var values = [];
			cursor.onsuccess = function(e) {
				var cursor = e.target.result;
				if (cursor) {
					var data;
					if (!self._supportsBlobs) {
						data = dataURLToBlob(cursor.value.data)
					} else {
						data = cursor.value.data;
					}
					values.push({
						data: data,
						docKey: docKey,
						attachKey: cursor.primaryKey.split('/')[1] // TODO
					});
					cursor.continue();
				} else {
					deferred.resolve(values);
				}
			};

			cursor.onerror = function(e) {
				deferred.reject(e);
			};

			return deferred.promise;
		},

		getAllAttachmentURLs: function(docKey) {
			var deferred = Q.defer();
			this.getAllAttachments(docKey).then(function(attachments) {
				var urls = attachments.map(function(a) {
					a.url = URL.createObjectURL(a.data);
					delete a.data;
					return a;
				});

				deferred.resolve(urls);
			}, function(e) {
				deferred.reject(e);
			});

			return deferred.promise;
		},

		getAttachmentURL: function(docKey, attachKey) {
			var deferred = Q.defer();
			this.getAttachment(docKey, attachKey).then(function(attachment) {
				deferred.resolve(URL.createObjectURL(attachment));
			}, function(e) {
				deferred.reject(e);
			});

			return deferred.promise;
		},

		revokeAttachmentURL: function(url) {
			URL.revokeObjectURL(url);
		},

		setAttachment: function(docKey, attachKey, data) {
			var deferred = Q.defer();

			if (data instanceof Blob && !this._supportsBlobs) {
				var self = this;
				convertToBase64(data, function(data) {
					continuation.call(self, data);
				});
			} else {
				continuation.call(this, data);
			}

			function continuation(data) {
				var obj = {
					path: docKey + '/' + attachKey,
					fname: docKey,
					data: data
				};
				var transaction = this._db.transaction(['attachments'], 'readwrite');
				var put = transaction.objectStore('attachments').put(obj);

				put.onsuccess = function(e) {
					deferred.resolve(e);
				};

				put.onerror = function(e) {
					deferred.reject(e);
				};
			}

			return deferred.promise;
		},

		rmAttachment: function(docKey, attachKey) {
			var deferred = Q.defer();
			var transaction = this._db.transaction(['attachments'], 'readwrite');
			var del = transaction.objectStore('attachments').delete(docKey + '/' + attachKey);

			del.onsuccess = function(e) {
				deferred.resolve(e);
			};

			del.onerror = function(e) {
				deferred.reject(e);
			};

			return deferred.promise;
		}
	};

	return {
		init: function(config) {
			var deferred = Q.defer();
			var dbVersion = 2;

			if (!indexedDB || !IDBTransaction) {
				deferred.reject("No IndexedDB");
				return deferred.promise;
			}

			var request = indexedDB.open(config.name, dbVersion);

			function createObjectStore(db) {
				db.createObjectStore("files");
				var attachStore = db.createObjectStore("attachments", {keyPath: 'path'});
				attachStore.createIndex('fname', 'fname', {unique: false})
			}

			// TODO: normalize errors
			request.onerror = function (event) {
				deferred.reject(event);
			};
		 
			request.onsuccess = function (event) {
				var db = request.result;
		 
				db.onerror = function (event) {
					console.log(event);
				};
				
				// Chrome workaround
				if (db.setVersion) {
					if (db.version != dbVersion) {
						var setVersion = db.setVersion(dbVersion);
						setVersion.onsuccess = function () {
							createObjectStore(db);
							deferred.resolve();
						};
					}
					else {
						deferred.resolve(new IDB(db));
					}
				} else {
					deferred.resolve(new IDB(db));
				}
			}
			
			request.onupgradeneeded = function (event) {
				createObjectStore(event.target.result);
			};

			return deferred.promise;
		},

		isAvailable: function() {
			return indexedDB != null && IDBTransaction != null;
		}
	}
})(Q);
var LocalStorageProvider = (function(Q) {
	return {
		init: function() {
			return Q({type: 'LocalStorage'});
		}
	}
})(Q);
var openDb = window.openDatabase;
var WebSQLProvider = (function(Q) {
	var URL = window.URL || window.webkitURL;
	var convertToBase64 = utils.convertToBase64;
	var dataURLToBlob = utils.dataURLToBlob;

	function WSQL(db) {
		this._db = db;
		this.type = 'WebSQL';
	}

	WSQL.prototype = {
		getContents: function(docKey, options) {
			var deferred = Q.defer();
			this._db.transaction(function(tx) {
				tx.executeSql('SELECT value FROM files WHERE fname = ?', [docKey],
				function(tx, res) {
					if (res.rows.length == 0) {
						deferred.resolve(undefined);
					} else {
						var data = res.rows.item(0).value;
						if (options && options.json)
							data = JSON.parse(data);
						deferred.resolve(data);
					}
				});
			}, function(err) {
				consol.log(err);
				deferred.reject(err);
			});

			return deferred.promise;
		},

		setContents: function(docKey, data, options) {
			var deferred = Q.defer();
			if (options && options.json)
				data = JSON.stringify(data);

			this._db.transaction(function(tx) {
				tx.executeSql(
				'INSERT OR REPLACE INTO files (fname, value) VALUES(?, ?)', [docKey, data]);
			}, function(err) {
				console.log(err);
				deferred.reject(err);
			}, function() {
				deferred.resolve();
			});

			return deferred.promise;
		},

		rm: function(docKey) {
			var deferred = Q.defer();

			this._db.transaction(function(tx) {
				tx.executeSql('DELETE FROM files WHERE fname = ?', [docKey]);
				tx.executeSql('DELETE FROM attachments WHERE fname = ?', [docKey]);
			}, function(err) {
				console.log(err);
				deferred.reject(err);
			}, function() {
				deferred.resolve();
			});

			return deferred.promise;
		},

		getAttachment: function(fname, akey) {
			var deferred = Q.defer();

			this._db.transaction(function(tx){ 
				tx.executeSql('SELECT value FROM attachments WHERE fname = ? AND akey = ?',
				[fname, akey],
				function(tx, res) {
					if (res.rows.length == 0) {
						deferred.resolve(undefined);
					} else {
						deferred.resolve(dataURLToBlob(res.rows.item(0).value));
					}
				});
			}, function(err) {
				deferred.reject(err);
			});

			return deferred.promise;
		},

		getAttachmentURL: function(docKey, attachKey) {
			var deferred = Q.defer();
			this.getAttachment(docKey, attachKey).then(function(blob) {
				deferred.resolve(URL.createObjectURL(blob));
			}, function() {
				deferred.reject();
			});

			return deferred.promise;
		},

		ls: function(docKey) {
			var deferred = Q.defer();

			var select;
			var field;
			if (!docKey) {
				select = 'SELECT fname FROM files';
				field = 'fname';
			} else {
				select = 'SELECT akey FROM attachments WHERE fname = ?';
				field = 'akey';
			}

			this._db.transaction(function(tx) {
				tx.executeSql(select, docKey ? [docKey] : [],
				function(tx, res) {
					var listing = [];
					for (var i = 0; i < res.rows.length; ++i) {
						listing.push(res.rows.item(i)[field]);
					}

					deferred.resolve(listing);
				}, function(err) {
					deferred.reject(err);
				});
			});

			return deferred.promise;
		},

		clear: function() {
			var deffered1 = Q.defer();
			var deffered2 = Q.defer();

			this._db.transaction(function(tx) {
				tx.executeSql('DELETE FROM files', function() {
					deffered1.resolve();
				});
				tx.executeSql('DELETE FROM attachments', function() {
					deffered2.resolve();
				});
			}, function(err) {
				deffered1.reject(err);
				deffered2.reject(err);
			});

			return Q.all([deffered1, deffered2]);
		},

		getAllAttachments: function(fname) {
			var deferred = Q.defer();

			this._db.transaction(function(tx) {
				tx.executeSql('SELECT value, akey FROM attachments WHERE fname = ?',
				[fname],
				function(tx, res) {
					// TODO: ship this work off to a webworker
					// since there could be many of these conversions?
					var result = [];
					for (var i = 0; i < res.rows.length; ++i) {
						var item = res.rows.item(i);
						result.push({
							docKey: fname,
							attachKey: item.akey,
							data: dataURLToBlob(item.value)
						});
					}

					deferred.resolve(result);
				});
			}, function(err) {
				deferred.reject(err);
			});

			return deferred.promise;
		},

		getAllAttachmentURLs: function(fname) {
			var deferred = Q.defer();
			this.getAllAttachments(fname).then(function(attachments) {
				var urls = attachments.map(function(a) {
					a.url = URL.createObjectURL(a.data);
					delete a.data;
					return a;
				});

				deferred.resolve(urls);
			}, function(e) {
				deferred.reject(e);
			});

			return deferred.promise;
		},

		revokeAttachmentURL: function(url) {
			URL.revokeObjectURL(url);
		},

		setAttachment: function(fname, akey, data) {
			var deferred = Q.defer();

			var self = this;
			convertToBase64(data, function(data) {
				self._db.transaction(function(tx) {
					tx.executeSql(
					'INSERT OR REPLACE INTO attachments (fname, akey, value) VALUES(?, ?, ?)',
					[fname, akey, data]);
				}, function(err) {
					deferred.reject(err);
				}, function() {
					deferred.resolve();
				});
			});

			return deferred.promise;
		},

		rmAttachment: function(fname, akey) {
			var deferred = Q.defer();
			this._db.transaction(function(tx) {
				tx.executeSql('DELETE FROM attachments WHERE fname = ? AND akey = ?',
				[fname, akey]);
			}, function(err) {
				deferred.reject(err);
			}, function() {
				deferred.resolve();
			});

			return deferred.promise;
		}
	};

	return {
		init: function(config) {
			var deferred = Q.defer();
			if (!openDb) {
				deferred.reject("No WebSQL");
				return deferred.promise;
			}

			var db = openDb(config.name, '1.0', 'large local storage', config.size);

			db.transaction(function(tx) {
				tx.executeSql('CREATE TABLE IF NOT EXISTS files (fname unique, value)');
				tx.executeSql('CREATE TABLE IF NOT EXISTS attachments (fname, akey, value)');
				tx.executeSql('CREATE INDEX IF NOT EXISTS fname_index ON attachments (fname)');
				tx.executeSql('CREATE INDEX IF NOT EXISTS akey_index ON attachments (akey)');
				tx.executeSql('CREATE UNIQUE INDEX IF NOT EXISTS uniq_attach ON attachments (fname, akey)')
			}, function(err) {
				deferred.reject(err);
			}, function() {
				deferred.resolve(new WSQL(db));
			});

			return deferred.promise;
		},

		isAvailable: function() {
			return openDb != null;
		}
	}
})(Q);
var LargeLocalStorage = (function(Q) {
	var sessionMeta = localStorage.getItem('LargeLocalStorage-meta');
	if (sessionMeta)
		sessionMeta = JSON.parse(sessionMeta);
	else
		sessionMeta = {};

	window.addEventListener('beforeunload', function() {
		localStorage.setItem('LargeLocalStorage-meta', JSON.stringify(sessionMeta));
	});

	function defaults(options, defaultOptions) {
		for (var k in defaultOptions) {
			if (options[k] === undefined)
				options[k] = defaultOptions[k];
		}

		return options;
	}

	var providers = {
		FileSystemAPI: FilesystemAPIProvider,
		IndexedDB: IndexedDBProvider,
		WebSQL: WebSQLProvider
		// LocalStorage: LocalStorageProvider
	}

	var defaultConfig = {
		size: 10 * 1024 * 1024,
		name: 'lls'
	};

	function selectImplementation(config) {
		if (!config) config = {};
		config = defaults(config, defaultConfig);

		if (config.forceProvider) {
			return providers[config.forceProvider].init(config);
		}

		return FilesystemAPIProvider.init(config).then(function(impl) {
			return Q(impl);
		}, function() {
			return IndexedDBProvider.init(config);
		}).then(function(impl) {
			return Q(impl);
		}, function() {
			return WebSQLProvider.init(config);
		}).then(function(impl) {
			return Q(impl);
		}, function() {
			console.error('Unable to create any storage implementations.  Using LocalStorage');
			return LocalStorageProvider.init(config);
		});
	}

	function copy(obj) {
		var result = {};
		Object.keys(obj).forEach(function(key) {
			result[key] = obj[key];
		});

		return result;
	}

	function handleDataMigration(storageInstance, config, previousProviderType, currentProivderType) {
		var previousProviderType = 
			sessionMeta[config.name] && sessionMeta[config.name].lastStorageImpl;
		if (config.migrate) {
			if (previousProviderType != currentProivderType
				&& previousProviderType in providers) {
				config = copy(config);
				config.forceProvider = previousProviderType;
				selectImplementation(config).then(function(prevImpl) {
					config.migrate(null, prevImpl, storageInstance, config);
				}, function(e) {
					config.migrate(e);
				});
			} else {
				if (config.migrationComplete)
					config.migrationComplete();
			}
		}
	}

	/**
	 * 
	 * LargeLocalStorage (or LLS) gives you a large capacity 
	 * (up to several gig with permission from the user)
	 * key-value store in the browser.
	 *
	 * For storage, LLS uses the [FilesystemAPI](https://developer.mozilla.org/en-US/docs/WebGuide/API/File_System)
	 * when running in Chrome and Opera, 
	 * [IndexedDB](https://developer.mozilla.org/en-US/docs/IndexedDB) in Firefox and IE
	 * and [WebSQL](http://www.w3.org/TR/webdatabase/) in Safari.
	 *
	 * When IndexedDB becomes available in Safari, LLS will
	 * update to take advantage of that storage implementation.
	 *
	 *
	 * Upon construction a LargeLocalStorage (LLS) object will be 
	 * immediately returned but not necessarily immediately ready for use.
	 *
	 * A LLS object has an `initialized` property which is a promise
	 * that is resolved when the LLS object is ready for us.
	 *
	 * Usage of LLS would typically be:
	 * ```
	 * var storage = new LargeLocalStorage({size: 75*1024*1024});
	 * storage.initialized.then(function(grantedCapacity) {
	 *   // storage ready to be used.
	 * });
	 * ```
	 *
	 * The reason that LLS may not be immediately ready for
	 * use is that some browsers require confirmation from the
	 * user before a storage area may be created.  Also,
	 * the browser's native storage APIs are asynchronous.
	 *
	 * If an LLS instance is used before the storage
	 * area is ready then any
	 * calls to it will throw an exception with code: "NO_IMPLEMENTATION"
	 *
	 * This behavior is useful when you want the application
	 * to continue to function--regardless of whether or
	 * not the user has allowed it to store data--and would
	 * like to know when your storage calls fail at the point
	 * of those calls.
	 *
	 * LLS-contrib has utilities to queue storage calls until
	 * the implementation is ready.  If an implementation
	 * is never ready this could obviously lead to memory issues
	 * which is why it is not the default behavior.
	 *
	 * @example
	 *	var desiredCapacity = 50 * 1024 * 1024; // 50MB
	 *	var storage = new LargeLocalStorage({
	 *		// desired capacity, in bytes.
	 *		size: desiredCapacity,
	 *
	 * 		// optional name for your LLS database. Defaults to lls.
	 *		// This is the name given to the underlying
	 *		// IndexedDB or WebSQL DB or FSAPI Folder.
	 *		// LLS's with different names are independent.
	 *		name: 'myStorage'
	 *
	 *		// the following is an optional param 
	 *		// that is useful for debugging.
	 *		// force LLS to use a specific storage implementation
	 *		// forceProvider: 'IndexedDB' or 'WebSQL' or 'FilesystemAPI'
	 *		
	 *		// These parameters can be used to migrate data from one
	 *		// storage implementation to another
	 *		// migrate: LargeLocalStorage.copyOldData,
	 *		// migrationComplete: function(err) {
	 *		//   db is initialized and old data has been copied.
	 *		// }
	 *	});
	 *	storage.initialized.then(function(capacity) {
	 *		if (capacity != -1 && capacity != desiredCapacity) {
	 *			// the user didn't authorize your storage request
	 *			// so instead you have some limitation on your storage
	 *		}
	 *	})
	 *
	 * @class LargeLocalStorage
	 * @constructor
	 * @param {object} config {size: sizeInByes, [forceProvider: force a specific implementation]}
	 * @return {LargeLocalStorage}
	 */
	function LargeLocalStorage(config) {
		var deferred = Q.defer();
		/**
		* @property {promise} initialized
		*/
		this.initialized = deferred.promise;

		var piped = createPipeline([
			'ready',
			'ls',
			'rm',
			'clear',
			'getContents',
			'setContents',
			'getAttachment',
			'setAttachment',
			'getAttachmentURL',
			'getAllAttachments',
			'getAllAttachmentURLs',
			'revokeAttachmentURL',
			'rmAttachment',
			'getCapacity',
			'initialized']);

		piped.pipe.addLast('lls', this);
		piped.initialized = this.initialized;

		var self = this;
		selectImplementation(config).then(function(impl) {
			self._impl = impl;
			handleDataMigration(piped, config, self._impl.type);
			sessionMeta[config.name] = sessionMeta[config.name] || {};
			sessionMeta[config.name].lastStorageImpl = impl.type;
			deferred.resolve(piped);
		}).catch(function(e) {
			// This should be impossible
			console.log(e);
			deferred.reject('No storage provider found');
		});

		return piped;
	}

	LargeLocalStorage.prototype = {
		/**
		* Whether or not LLS is ready to store data.
		* The `initialized` property can be used to
		* await initialization.
		* @example
		*	// may or may not be true
		*	storage.ready();
		*	
		*	storage.initialized.then(function() {
		*		// always true
		*		storage.ready();
		*	})
		* @method ready
		*/
		ready: function() {
			return this._impl != null;
		},

		/**
		* List all attachments under a given key.
		*
		* List all documents if no key is provided.
		*
		* Returns a promise that is fulfilled with
		* the listing.
		*
		* @example
		*	storage.ls().then(function(docKeys) {
		*		console.log(docKeys);
		*	})
		*
		* @method ls
		* @param {string} [docKey]
		* @returns {promise} resolved with the listing, rejected if the listing fails.
		*/
		ls: function(docKey) {
			this._checkAvailability();
			return this._impl.ls(docKey);
		},

		/**
		* Remove the specified document and all
		* of its attachments.
		*
		* Returns a promise that is fulfilled when the
		* removal completes.
		*
		* If no docKey is specified, this throws an error.
		*
		* To remove all files in LargeLocalStorage call
		* `lls.clear();`
		*
		* To remove all attachments that were written without
		* a docKey, call `lls.rm('__emptydoc__');`
		*
		* rm works this way to ensure you don't lose
		* data due to an accidently undefined variable.
		*
		* @example
		* 	stoarge.rm('exampleDoc').then(function() {
		*		alert('doc and all attachments were removed');
		* 	})
		*
		* @method rm
		* @param {string} docKey
		* @returns {promise} resolved when removal completes, rejected if the removal fails.
		*/
		rm: function(docKey) {
			this._checkAvailability();
			return this._impl.rm(docKey);
		},

		/**
		* An explicit way to remove all documents and
		* attachments from LargeLocalStorage.
		*
		* @example
		*	storage.clear().then(function() {
		*		alert('all data has been removed');
		*	});
		* 
		* @returns {promise} resolve when clear completes, rejected if clear fails.
		*/
		clear: function() {
			this._checkAvailability();
			return this._impl.clear();
		},

		/**
		* Get the contents of a document identified by `docKey`
		* TODO: normalize all implementations to allow storage
		* and retrieval of JS objects?
		*
		* @example
		* 	storage.getContents('exampleDoc').then(function(contents) {
		* 		alert(contents);
		* 	});
		*
		* @method getContents
		* @param {string} docKey
		* @returns {promise} resolved with the contents when the get completes
		*/
		getContents: function(docKey, options) {
			this._checkAvailability();
			return this._impl.getContents(docKey, options);
		},

		/**
		* Set the contents identified by `docKey` to `data`.
		* The document will be created if it does not exist.
		*
		* @example
		* 	storage.setContents('exampleDoc', 'some data...').then(function() {
		*		alert('doc written');
		* 	});
		*
		* @method setContents
		* @param {string} docKey
		* @param {any} data
		* @returns {promise} fulfilled when set completes
		*/
		setContents: function(docKey, data, options) {
			this._checkAvailability();
			return this._impl.setContents(docKey, data, options);
		},

		/**
		* Get the attachment identified by `docKey` and `attachKey`
		*
		* @example
		* 	storage.getAttachment('exampleDoc', 'examplePic').then(function(attachment) {
		*    	var url = URL.createObjectURL(attachment);
		*    	var image = new Image(url);
		*    	document.body.appendChild(image);
		*    	URL.revokeObjectURL(url);
		* 	})
		*
		* @method getAttachment
		* @param {string} [docKey] Defaults to `__emptydoc__`
		* @param {string} attachKey key of the attachment
		* @returns {promise} fulfilled with the attachment or
		* rejected if it could not be found.  code: 1
		*/
		getAttachment: function(docKey, attachKey) {
			if (!docKey) docKey = '__emptydoc__';
			this._checkAvailability();
			return this._impl.getAttachment(docKey, attachKey);
		},

		/**
		* Set an attachment for a given document.  Identified
		* by `docKey` and `attachKey`.
		*
		* @example
		* 	storage.setAttachment('myDoc', 'myPic', blob).then(function() {
		*    	alert('Attachment written');
		* 	})
		*
		* @method setAttachment
		* @param {string} [docKey] Defaults to `__emptydoc__`
		* @param {string} attachKey key for the attachment
		* @param {any} attachment data
		* @returns {promise} resolved when the write completes.  Rejected
		* if an error occurs.
		*/
		setAttachment: function(docKey, attachKey, data) {
			if (!docKey) docKey = '__emptydoc__';
			this._checkAvailability();
			return this._impl.setAttachment(docKey, attachKey, data);
		},

		/**
		* Get the URL for a given attachment.
		*
		* @example
		* 	storage.getAttachmentURL('myDoc', 'myPic').then(function(url) {
	 	*   	var image = new Image();
	 	*   	image.src = url;
	 	*   	document.body.appendChild(image);
	 	*   	storage.revokeAttachmentURL(url);
		* 	})
		*
		* This is preferrable to getting the attachment and then getting the
		* URL via `createObjectURL` (on some systems) as LLS can take advantage of 
		* lower level details to improve performance.
		*
		* @method getAttachmentURL
		* @param {string} [docKey] Identifies the document.  Defaults to `__emptydoc__`
		* @param {string} attachKey Identifies the attachment.
		* @returns {promose} promise that is resolved with the attachment url.
		*/
		getAttachmentURL: function(docKey, attachKey) {
			if (!docKey) docKey = '__emptydoc__';
			this._checkAvailability();
			return this._impl.getAttachmentURL(docKey, attachKey);
		},

		/**
		* Gets all of the attachments for a document.
		*
		* @example
		* 	storage.getAllAttachments('exampleDoc').then(function(attachEntries) {
		* 		attachEntries.map(function(entry) {
		*			var a = entry.data;
		*			// do something with it...
		* 			if (a.type.indexOf('image') == 0) {
		*				// show image...
		*			} else if (a.type.indexOf('audio') == 0) {
		*				// play audio...
		*			} else ...
		*		})
		* 	})
		*
		* @method getAllAttachments
		* @param {string} [docKey] Identifies the document.  Defaults to `__emptydoc__`
		* @returns {promise} Promise that is resolved with all of the attachments for
		* the given document.
		*/
		getAllAttachments: function(docKey) {
			if (!docKey) docKey = '__emptydoc__';
			this._checkAvailability();
			return this._impl.getAllAttachments(docKey);
		},

		/**
		* Gets all attachments URLs for a document.
		*
		* @example
		* 	storage.getAllAttachmentURLs('exampleDoc').then(function(urlEntries) {
		*		urlEntries.map(function(entry) {
		*			var url = entry.url;
		* 			// do something with the url...
		* 		})
		* 	})
		*
		* @method getAllAttachmentURLs
		* @param {string} [docKey] Identifies the document.  Defaults to the `__emptydoc__` document.
		* @returns {promise} Promise that is resolved with all of the attachment
		* urls for the given doc.
		*/
		getAllAttachmentURLs: function(docKey) {
			if (!docKey) docKey = '__emptydoc__';
			this._checkAvailability();
			return this._impl.getAllAttachmentURLs(docKey);
		},

		/**
		* Revoke the attachment URL as required by the underlying
		* storage system.
		*
		* This is akin to `URL.revokeObjectURL(url)`
		* URLs that come from `getAttachmentURL` or `getAllAttachmentURLs` 
		* should be revoked by LLS and not `URL.revokeObjectURL`
		*
		* @example
		* 	storage.getAttachmentURL('doc', 'attach').then(function(url) {
		*		// do something with the URL
		*		storage.revokeAttachmentURL(url);
		* 	})
		*
		* @method revokeAttachmentURL
		* @param {string} url The URL as returned by `getAttachmentURL` or `getAttachmentURLs`
		* @returns {void}
		*/
		revokeAttachmentURL: function(url) {
			this._checkAvailability();
			return this._impl.revokeAttachmentURL(url);
		},

		/**
		* Remove an attachment from a document.
		*
		* @example
		* 	storage.rmAttachment('exampleDoc', 'someAttachment').then(function() {
		* 		alert('exampleDoc/someAttachment removed');
		* 	}).catch(function(e) {
		*		alert('Attachment removal failed: ' + e);
		* 	});
		*
		* @method rmAttachment
		* @param {string} docKey
		* @param {string} attachKey
		* @returns {promise} Promise that is resolved once the remove completes
		*/
		rmAttachment: function(docKey, attachKey) {
			if (!docKey) docKey = '__emptydoc__';
			this._checkAvailability();
			return this._impl.rmAttachment(docKey, attachKey);
		},

		/**
		* Returns the actual capacity of the storage or -1
		* if it is unknown.  If the user denies your request for
		* storage you'll get back some smaller amount of storage than what you
		* actually requested.
		*
		* TODO: return an estimated capacity if actual capacity is unknown?
		* -Firefox is 50MB until authorized to go above,
		* -Chrome is some % of available disk space,
		* -Safari unlimited as long as the user keeps authorizing size increases
		* -Opera same as safari?
		*
		* @example
		*	// the initialized property will call you back with the capacity
		* 	storage.initialized.then(function(capacity) {
		*		console.log('Authorized to store: ' + capacity + ' bytes');
		* 	});
		*	// or if you know your storage is already available
		*	// you can call getCapacity directly
		*	storage.getCapacity()
		*
		* @method getCapacity
		* @returns {number} Capacity, in bytes, of the storage.  -1 if unknown.
		*/
		getCapacity: function() {
			this._checkAvailability();
			if (this._impl.getCapacity)
				return this._impl.getCapacity();
			else
				return -1;
		},

		_checkAvailability: function() {
			if (!this._impl) {
				throw {
					msg: "No storage implementation is available yet.  The user most likely has not granted you app access to FileSystemAPI or IndexedDB",
					code: "NO_IMPLEMENTATION"
				};
			}
		}
	};

	LargeLocalStorage.contrib = {};

	function writeAttachments(docKey, attachments, storage) {
		var promises = [];
		attachments.forEach(function(attachment) {
			promises.push(storage.setAttachment(docKey, attachment.attachKey, attachment.data));
		});

		return Q.all(promises);
	}

	function copyDocs(docKeys, oldStorage, newStorage) {
		var promises = [];
		docKeys.forEach(function(key) {
			promises.push(oldStorage.getContents(key).then(function(contents) {
				return newStorage.setContents(key, contents);
			}));
		});

		docKeys.forEach(function(key) {
			promises.push(oldStorage.getAllAttachments(key).then(function(attachments) {
				return writeAttachments(key, attachments, newStorage);
			}));
		});

		return Q.all(promises);
	}

	LargeLocalStorage.copyOldData = function(err, oldStorage, newStorage, config) {
		if (err) {
			throw err;
		}

		oldStorage.ls().then(function(docKeys) {
			return copyDocs(docKeys, oldStorage, newStorage)
		}).then(function() {
			if (config.migrationComplete)
				config.migrationComplete();
		}, function(e) {
			config.migrationComplete(e);
		});
	};

	LargeLocalStorage._sessionMeta = sessionMeta;
	
	var availableProviders = [];
	Object.keys(providers).forEach(function(potentialProvider) {
		if (providers[potentialProvider].isAvailable())
			availableProviders.push(potentialProvider);
	});

	LargeLocalStorage.availableProviders = availableProviders;

	return LargeLocalStorage;
})(Q);

	return LargeLocalStorage;
}

if (typeof define === 'function' && define.amd) {
	define(['Q'], definition);
} else {
	glob.LargeLocalStorage = definition.call(glob, Q);
}

}).call(this, this);

================================================
FILE: dist/contrib/S3Link.js
================================================
LargeLocalStorage.contrib.S3Link = (function() {
	function S3Link(config) {

	}

	S3Link.prototype = {
		push: function(docKey, options) {

		}
	};

	return S3Link;
})();

================================================
FILE: dist/contrib/URLCache.js
================================================
LargeLocalStorage.contrib.URLCache = (function() {
	var defaultOptions = {
		manageRevocation: true
	};

	function defaults(options, defaultOptions) {
		for (var k in defaultOptions) {
			if (options[k] === undefined)
				options[k] = defaultOptions[k];
		}

		return options;
	}

	function add(docKey, attachKey, url) {
		if (this.options.manageRevocation)
			expunge.call(this, docKey, attachKey, true);

		var mainCache = this.cache.main;
		var docCache = mainCache[docKey];
		if (!docCache) {
			docCache = {};
			mainCache[docKey] = docCache;
		}

		docCache[attachKey] = url;
		this.cache.reverse[url] = {docKey: docKey, attachKey: attachKey};
	}

	function addAll(urlEntries) {
		urlEntries.forEach(function(entry) {
			add.call(this, entry.docKey, entry.attachKey, entry.url);
		}, this);
	}

	function expunge(docKey, attachKey, needsRevoke) {
		function delAndRevoke(attachKey) {
			var url = docCache[attachKey];
			delete docCache[attachKey];
			delete this.cache.reverse[url];
			if (this.options.manageRevocation && needsRevoke)
				this.llshandler.revokeAttachmentURL(url, {bypassUrlCache: true});
		}

		var docCache = this.cache.main[docKey];
		if (docCache) {
			if (attachKey) {
				delAndRevoke.call(this, attachKey);
			} else {
				for (var attachKey in docCache) {
					delAndRevoke.call(this, attachKey);
				}
				delete this.cache.main[docKey];
			}
		}
	}

	function expungeByUrl(url) {
		var keys = this.cache.reverse[url];
		if (keys) {
			expunge.call(this, keys.docKey, keys.attachKey, false);
		}
	}


	function URLCache(llspipe, options) {
		options = options || {};
		this.options = defaults(options, defaultOptions);
		this.llshandler = llspipe.pipe.getHandler('lls');
		this.pending = {};
		this.cache = {
			main: {},
			reverse: {}
		};
	}

	URLCache.prototype = {
		setAttachment: function(docKey, attachKey, blob) {
			expunge.call(this, docKey, attachKey);
			return this.__pipectx.next(docKey, attachKey, blob);
		},

		rmAttachment: function(docKey, attachKey) {
			expunge.call(this, docKey, attachKey);
			return this.__pipectx.next(docKey, attachKey);
		},

		rm: function(docKey) {
			expunge.call(this, docKey);
			return this.__pipectx.next(docKey);
		},

		revokeAttachmentURL: function(url, options) {
			if (!options || !options.bypassUrlCache)
				expungeByUrl.call(this, url);

			return this.__pipectx.next(url, options);
		},

		getAttachmentURL: function(docKey, attachKey) {
			var pendingKey = docKey + attachKey;
			var pending = this.pending[pendingKey];
			if (pending)
				return pending;

			var promise = this.__pipectx.next(docKey, attachKey);
			var self = this;
			promise.then(function(url) {
				add.call(self, docKey, attachKey, url);
				delete self.pending[pendingKey];
			});

			this.pending[pendingKey] = promise;

			return promise;
		},

		// TODO: pending between this and getAttachmentURL...
		// Execute this as an ls and then
		// a loop on getAttachmentURL instead???
		// doing it the way mentiond above
		// will prevent us from leaking blobs.
		getAllAttachmentURLs: function(docKey) {
			var promise = this.__pipectx.next(docKey);
			var self = this;
			promise.then(function(urlEntries) {
				addAll.call(self, urlEntries);
			});

			return promise;
		},

		clear: function() {
			this.revokeAllCachedURLs();
			return this.__pipectx.next();
		},

		revokeAllCachedURLs: function() {
			for (var url in this.cache.reverse) {
				this.llshandler.revokeAttachmentURL(url, {bypassUrlCache: true});
			}

			this.cache.reverse = {};
			this.cache.main = {};
		}
	};

	return {
		addTo: function(lls, options) {
			var cache = new URLCache(lls, options);
			lls.pipe.addFirst('URLCache', cache);
			return lls;
		}
	}
})();

================================================
FILE: examples/album/app.js
================================================
(function() {
	'use strict';

	var storage = new LargeLocalStorage({
		size: 20 * 1024 * 1024,
		name: 'lls-album-example'
		// forceProvider: 'IndexedDB'
		// forceProvider: 'WebSQL'
	});

	storage.initialized.then(function() {
		console.log(storage.getCapacity());
		var $storageNotice = $('.storageNotice');
		$storageNotice.css('opacity', 0);
		setTimeout(function() {
			$storageNotice.css('display', 'none');	
		}, 1100);

		bind();
	}, function() {
		console.log('denied');
	});

	function bind() {
		var dndArea = new Album($('.dndArea'));
	}

	function Album($el) {
		this.$el = $el;
		this._drop = this._drop.bind(this);
		this._photoAdded = this._photoAdded.bind(this);
		this._appendImage = this._appendImage.bind(this);
		this.$el.on('dragover', copyDragover);
		this.$el.on('drop', this._drop);
		this.$thumbs = this.$el.find('.thumbnails');
		this.$usage = this.$el.find('.usage');

		var self = this;
		$('#clear').click(function() {
			storage.clear().then(function() {
				self.$thumbs.empty();
			}).done();
		});

		this._renderExistingPhotos();
	}

	Album.prototype = {
		_drop: function(e) {
			e.stopPropagation();
			e.preventDefault();

			e = e.originalEvent;

			foreach(this._photoAdded, keep(isImage, e.dataTransfer.files));
		},

		_photoAdded: function(file) {
			// TOOD: see if already exists??
			storage.setAttachment('album', file.name, file)
			.then(function() {
				return storage.getAttachmentURL('album', file.name);
			}).then(this._appendImage);
		},

		_appendImage: function(url) {
			if (this.$usage) {
				this.$usage.remove();
				this.$usage = null;
			}
			var container = $('<div class="col-sm-6 col-md-3"></div>');
			var image = new Image();
			image.src = url;
			var self = this;
			image.onload = function() {
				var scale = 171 / image.naturalWidth;
				
				var newHeight = scale * image.naturalHeight;
				if (newHeight > 180) {
					scale = 180 / image.naturalHeight;
					newHeight = 180;
				}

				var newWidth = scale * image.naturalWidth;

				image.width = newWidth;
				image.height = newHeight;

				container.append(image);
				self.$thumbs.append(container);
			};

			storage.revokeAttachmentURL(url);
		},

		_renderExistingPhotos: function() {
			var self = this;
			storage.getAllAttachmentURLs('album')
			.then(function(urls) {
				urls = urls.map(function(u) {
					return u.url;
				});
				foreach(self._appendImage, urls);
			});
		}
	};


	function copyDragover(e) {
		e.stopPropagation();
		e.preventDefault();
		e = e.originalEvent;
		e.dataTransfer.dropEffect = 'copy';
	}

	function foreach(cb, arr) {
		for (var i = 0; i < arr.length; ++i) {
			cb(arr[i]);
		}
	}

	function isImage(file) {
		return file.type.indexOf('image') == 0;
	}

	function keep(pred, arr) {
		return filter(not(pred), arr);
	}

	function not(pred) {
		return function(e) {
			return !pred(e);
		}
	}

	function filter(pred, arr) {
		var result = [];
		for (var i = 0; i < arr.length; ++i) {
			var e = arr[i];
			if (!pred(e))
				result.push(e);
		}

		return result;
	}
})();

================================================
FILE: examples/album/index.html
================================================
<!DOCTYPE html>
<html>
<head>
	<link rel="stylesheet" type="text/css" href="../../bower_components/bootstrap/dist/css/bootstrap.css">
	<link rel="stylesheet" type="text/css" href="main.css">
</head>
<body>
	<div class="container dndArea">
		<div class="usage">Drag and drop images to add to your album.</div>
		<div class="row thumbnails">
		</div>
	</div>

	<div class="storageNotice alert">
		<div class="container">
			In order to keep your photos you need to grant this app the ability
			to save your photos!  Please click accept on the browser's prompt.
		</div>
	</div>
	<a href="#!" id="clear">Clear Album</a>

	<script src="../../bower_components/q/q.js"></script>
	<script src="../../dist/LargeLocalStorage.js"></script>
	<script src="../../bower_components/jquery/jquery.js"></script>
	<script src="app.js"></script>
</body>
</html>


================================================
FILE: examples/album/main.css
================================================
.dndArea {
	margin-top: 20px;
	border: 2px dashed #bbb;
	border-radius: 5px;
	min-height: 240px;
	padding: 20px;
}

.storageNotice {
	position: absolute;
	width: 100%;
	height: 100%;
	top: 20px;
	text-align: center;
	-webkit-transition: opacity 1s;
	transition: all 1s;
}

.usage {
	width: 100%;
	height: 100%;
	text-align: center;
	font-size: 32px;
	color: #CCC;
}

================================================
FILE: package.json
================================================
{
  "name": "lls",
  "version": "0.1.3",
  "description": "Storage large files and blob in a cross platform way, in the browser",
  "main": "Gruntfile.js",
  "directories": {
    "test": "test"
  },
  "scripts": {
    "test": "test"
  },
  "repository": {
    "type": "git",
    "url": "git://github.com/tantaman/LargeLocalStorage.git"
  },
  "keywords": [
    "LocalStorage",
    "key",
    "value",
    "key-value",
    "storage",
    "browser",
    "indexeddb",
    "websql",
    "filesystemapi"
  ],
  "author": "Matt Crinklaw-Vogt",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/tantaman/LargeLocalStorage/issues"
  },
  "devDependencies": {
    "grunt": "~0.4.1",
    "grunt-contrib-requirejs": "~0.4.1",
    "matchdep": "~0.3.0",
    "grunt-contrib-concat": "~0.3.0",
    "grunt-contrib-watch": "~0.5.3",
    "grunt-contrib-connect": "~0.5.0",
    "grunt-contrib-yuidoc": "~0.5.0",
    "yuidoc-library-theme": "git://github.com/tantaman/yuidoc-library-theme.git",
    "grunt-contrib-copy": "~0.4.1",
    "grunt-contrib-uglify": "~0.2.4"
  }
}


================================================
FILE: src/LargeLocalStorage.js
================================================
var LargeLocalStorage = (function(Q) {
	var sessionMeta = localStorage.getItem('LargeLocalStorage-meta');
	if (sessionMeta)
		sessionMeta = JSON.parse(sessionMeta);
	else
		sessionMeta = {};

	window.addEventListener('beforeunload', function() {
		localStorage.setItem('LargeLocalStorage-meta', JSON.stringify(sessionMeta));
	});

	function defaults(options, defaultOptions) {
		for (var k in defaultOptions) {
			if (options[k] === undefined)
				options[k] = defaultOptions[k];
		}

		return options;
	}

	var providers = {
		FileSystemAPI: FilesystemAPIProvider,
		IndexedDB: IndexedDBProvider,
		WebSQL: WebSQLProvider
		// LocalStorage: LocalStorageProvider
	}

	var defaultConfig = {
		size: 10 * 1024 * 1024,
		name: 'lls'
	};

	function selectImplementation(config) {
		if (!config) config = {};
		config = defaults(config, defaultConfig);

		if (config.forceProvider) {
			return providers[config.forceProvider].init(config);
		}

		return FilesystemAPIProvider.init(config).then(function(impl) {
			return Q(impl);
		}, function() {
			return IndexedDBProvider.init(config);
		}).then(function(impl) {
			return Q(impl);
		}, function() {
			return WebSQLProvider.init(config);
		}).then(function(impl) {
			return Q(impl);
		}, function() {
			console.error('Unable to create any storage implementations.  Using LocalStorage');
			return LocalStorageProvider.init(config);
		});
	}

	function copy(obj) {
		var result = {};
		Object.keys(obj).forEach(function(key) {
			result[key] = obj[key];
		});

		return result;
	}

	function handleDataMigration(storageInstance, config, previousProviderType, currentProivderType) {
		var previousProviderType = 
			sessionMeta[config.name] && sessionMeta[config.name].lastStorageImpl;
		if (config.migrate) {
			if (previousProviderType != currentProivderType
				&& previousProviderType in providers) {
				config = copy(config);
				config.forceProvider = previousProviderType;
				selectImplementation(config).then(function(prevImpl) {
					config.migrate(null, prevImpl, storageInstance, config);
				}, function(e) {
					config.migrate(e);
				});
			} else {
				if (config.migrationComplete)
					config.migrationComplete();
			}
		}
	}

	/**
	 * 
	 * LargeLocalStorage (or LLS) gives you a large capacity 
	 * (up to several gig with permission from the user)
	 * key-value store in the browser.
	 *
	 * For storage, LLS uses the [FilesystemAPI](https://developer.mozilla.org/en-US/docs/WebGuide/API/File_System)
	 * when running in Chrome and Opera, 
	 * [IndexedDB](https://developer.mozilla.org/en-US/docs/IndexedDB) in Firefox and IE
	 * and [WebSQL](http://www.w3.org/TR/webdatabase/) in Safari.
	 *
	 * When IndexedDB becomes available in Safari, LLS will
	 * update to take advantage of that storage implementation.
	 *
	 *
	 * Upon construction a LargeLocalStorage (LLS) object will be 
	 * immediately returned but not necessarily immediately ready for use.
	 *
	 * A LLS object has an `initialized` property which is a promise
	 * that is resolved when the LLS object is ready for us.
	 *
	 * Usage of LLS would typically be:
	 * ```
	 * var storage = new LargeLocalStorage({size: 75*1024*1024});
	 * storage.initialized.then(function(grantedCapacity) {
	 *   // storage ready to be used.
	 * });
	 * ```
	 *
	 * The reason that LLS may not be immediately ready for
	 * use is that some browsers require confirmation from the
	 * user before a storage area may be created.  Also,
	 * the browser's native storage APIs are asynchronous.
	 *
	 * If an LLS instance is used before the storage
	 * area is ready then any
	 * calls to it will throw an exception with code: "NO_IMPLEMENTATION"
	 *
	 * This behavior is useful when you want the application
	 * to continue to function--regardless of whether or
	 * not the user has allowed it to store data--and would
	 * like to know when your storage calls fail at the point
	 * of those calls.
	 *
	 * LLS-contrib has utilities to queue storage calls until
	 * the implementation is ready.  If an implementation
	 * is never ready this could obviously lead to memory issues
	 * which is why it is not the default behavior.
	 *
	 * @example
	 *	var desiredCapacity = 50 * 1024 * 1024; // 50MB
	 *	var storage = new LargeLocalStorage({
	 *		// desired capacity, in bytes.
	 *		size: desiredCapacity,
	 *
	 * 		// optional name for your LLS database. Defaults to lls.
	 *		// This is the name given to the underlying
	 *		// IndexedDB or WebSQL DB or FSAPI Folder.
	 *		// LLS's with different names are independent.
	 *		name: 'myStorage'
	 *
	 *		// the following is an optional param 
	 *		// that is useful for debugging.
	 *		// force LLS to use a specific storage implementation
	 *		// forceProvider: 'IndexedDB' or 'WebSQL' or 'FilesystemAPI'
	 *		
	 *		// These parameters can be used to migrate data from one
	 *		// storage implementation to another
	 *		// migrate: LargeLocalStorage.copyOldData,
	 *		// migrationComplete: function(err) {
	 *		//   db is initialized and old data has been copied.
	 *		// }
	 *	});
	 *	storage.initialized.then(function(capacity) {
	 *		if (capacity != -1 && capacity != desiredCapacity) {
	 *			// the user didn't authorize your storage request
	 *			// so instead you have some limitation on your storage
	 *		}
	 *	})
	 *
	 * @class LargeLocalStorage
	 * @constructor
	 * @param {object} config {size: sizeInByes, [forceProvider: force a specific implementation]}
	 * @return {LargeLocalStorage}
	 */
	function LargeLocalStorage(config) {
		var deferred = Q.defer();
		/**
		* @property {promise} initialized
		*/
		this.initialized = deferred.promise;

		var piped = createPipeline([
			'ready',
			'ls',
			'rm',
			'clear',
			'getContents',
			'setContents',
			'getAttachment',
			'setAttachment',
			'getAttachmentURL',
			'getAllAttachments',
			'getAllAttachmentURLs',
			'revokeAttachmentURL',
			'rmAttachment',
			'getCapacity',
			'initialized']);

		piped.pipe.addLast('lls', this);
		piped.initialized = this.initialized;

		var self = this;
		selectImplementation(config).then(function(impl) {
			self._impl = impl;
			handleDataMigration(piped, config, self._impl.type);
			sessionMeta[config.name] = sessionMeta[config.name] || {};
			sessionMeta[config.name].lastStorageImpl = impl.type;
			deferred.resolve(piped);
		}).catch(function(e) {
			// This should be impossible
			console.log(e);
			deferred.reject('No storage provider found');
		});

		return piped;
	}

	LargeLocalStorage.prototype = {
		/**
		* Whether or not LLS is ready to store data.
		* The `initialized` property can be used to
		* await initialization.
		* @example
		*	// may or may not be true
		*	storage.ready();
		*	
		*	storage.initialized.then(function() {
		*		// always true
		*		storage.ready();
		*	})
		* @method ready
		*/
		ready: function() {
			return this._impl != null;
		},

		/**
		* List all attachments under a given key.
		*
		* List all documents if no key is provided.
		*
		* Returns a promise that is fulfilled with
		* the listing.
		*
		* @example
		*	storage.ls().then(function(docKeys) {
		*		console.log(docKeys);
		*	})
		*
		* @method ls
		* @param {string} [docKey]
		* @returns {promise} resolved with the listing, rejected if the listing fails.
		*/
		ls: function(docKey) {
			this._checkAvailability();
			return this._impl.ls(docKey);
		},

		/**
		* Remove the specified document and all
		* of its attachments.
		*
		* Returns a promise that is fulfilled when the
		* removal completes.
		*
		* If no docKey is specified, this throws an error.
		*
		* To remove all files in LargeLocalStorage call
		* `lls.clear();`
		*
		* To remove all attachments that were written without
		* a docKey, call `lls.rm('__emptydoc__');`
		*
		* rm works this way to ensure you don't lose
		* data due to an accidently undefined variable.
		*
		* @example
		* 	stoarge.rm('exampleDoc').then(function() {
		*		alert('doc and all attachments were removed');
		* 	})
		*
		* @method rm
		* @param {string} docKey
		* @returns {promise} resolved when removal completes, rejected if the removal fails.
		*/
		rm: function(docKey) {
			this._checkAvailability();
			return this._impl.rm(docKey);
		},

		/**
		* An explicit way to remove all documents and
		* attachments from LargeLocalStorage.
		*
		* @example
		*	storage.clear().then(function() {
		*		alert('all data has been removed');
		*	});
		* 
		* @returns {promise} resolve when clear completes, rejected if clear fails.
		*/
		clear: function() {
			this._checkAvailability();
			return this._impl.clear();
		},

		/**
		* Get the contents of a document identified by `docKey`
		* TODO: normalize all implementations to allow storage
		* and retrieval of JS objects?
		*
		* @example
		* 	storage.getContents('exampleDoc').then(function(contents) {
		* 		alert(contents);
		* 	});
		*
		* @method getContents
		* @param {string} docKey
		* @returns {promise} resolved with the contents when the get completes
		*/
		getContents: function(docKey, options) {
			this._checkAvailability();
			return this._impl.getContents(docKey, options);
		},

		/**
		* Set the contents identified by `docKey` to `data`.
		* The document will be created if it does not exist.
		*
		* @example
		* 	storage.setContents('exampleDoc', 'some data...').then(function() {
		*		alert('doc written');
		* 	});
		*
		* @method setContents
		* @param {string} docKey
		* @param {any} data
		* @returns {promise} fulfilled when set completes
		*/
		setContents: function(docKey, data, options) {
			this._checkAvailability();
			return this._impl.setContents(docKey, data, options);
		},

		/**
		* Get the attachment identified by `docKey` and `attachKey`
		*
		* @example
		* 	storage.getAttachment('exampleDoc', 'examplePic').then(function(attachment) {
		*    	var url = URL.createObjectURL(attachment);
		*    	var image = new Image(url);
		*    	document.body.appendChild(image);
		*    	URL.revokeObjectURL(url);
		* 	})
		*
		* @method getAttachment
		* @param {string} [docKey] Defaults to `__emptydoc__`
		* @param {string} attachKey key of the attachment
		* @returns {promise} fulfilled with the attachment or
		* rejected if it could not be found.  code: 1
		*/
		getAttachment: function(docKey, attachKey) {
			if (!docKey) docKey = '__emptydoc__';
			this._checkAvailability();
			return this._impl.getAttachment(docKey, attachKey);
		},

		/**
		* Set an attachment for a given document.  Identified
		* by `docKey` and `attachKey`.
		*
		* @example
		* 	storage.setAttachment('myDoc', 'myPic', blob).then(function() {
		*    	alert('Attachment written');
		* 	})
		*
		* @method setAttachment
		* @param {string} [docKey] Defaults to `__emptydoc__`
		* @param {string} attachKey key for the attachment
		* @param {any} attachment data
		* @returns {promise} resolved when the write completes.  Rejected
		* if an error occurs.
		*/
		setAttachment: function(docKey, attachKey, data) {
			if (!docKey) docKey = '__emptydoc__';
			this._checkAvailability();
			return this._impl.setAttachment(docKey, attachKey, data);
		},

		/**
		* Get the URL for a given attachment.
		*
		* @example
		* 	storage.getAttachmentURL('myDoc', 'myPic').then(function(url) {
	 	*   	var image = new Image();
	 	*   	image.src = url;
	 	*   	document.body.appendChild(image);
	 	*   	storage.revokeAttachmentURL(url);
		* 	})
		*
		* This is preferrable to getting the attachment and then getting the
		* URL via `createObjectURL` (on some systems) as LLS can take advantage of 
		* lower level details to improve performance.
		*
		* @method getAttachmentURL
		* @param {string} [docKey] Identifies the document.  Defaults to `__emptydoc__`
		* @param {string} attachKey Identifies the attachment.
		* @returns {promose} promise that is resolved with the attachment url.
		*/
		getAttachmentURL: function(docKey, attachKey) {
			if (!docKey) docKey = '__emptydoc__';
			this._checkAvailability();
			return this._impl.getAttachmentURL(docKey, attachKey);
		},

		/**
		* Gets all of the attachments for a document.
		*
		* @example
		* 	storage.getAllAttachments('exampleDoc').then(function(attachEntries) {
		* 		attachEntries.map(function(entry) {
		*			var a = entry.data;
		*			// do something with it...
		* 			if (a.type.indexOf('image') == 0) {
		*				// show image...
		*			} else if (a.type.indexOf('audio') == 0) {
		*				// play audio...
		*			} else ...
		*		})
		* 	})
		*
		* @method getAllAttachments
		* @param {string} [docKey] Identifies the document.  Defaults to `__emptydoc__`
		* @returns {promise} Promise that is resolved with all of the attachments for
		* the given document.
		*/
		getAllAttachments: function(docKey) {
			if (!docKey) docKey = '__emptydoc__';
			this._checkAvailability();
			return this._impl.getAllAttachments(docKey);
		},

		/**
		* Gets all attachments URLs for a document.
		*
		* @example
		* 	storage.getAllAttachmentURLs('exampleDoc').then(function(urlEntries) {
		*		urlEntries.map(function(entry) {
		*			var url = entry.url;
		* 			// do something with the url...
		* 		})
		* 	})
		*
		* @method getAllAttachmentURLs
		* @param {string} [docKey] Identifies the document.  Defaults to the `__emptydoc__` document.
		* @returns {promise} Promise that is resolved with all of the attachment
		* urls for the given doc.
		*/
		getAllAttachmentURLs: function(docKey) {
			if (!docKey) docKey = '__emptydoc__';
			this._checkAvailability();
			return this._impl.getAllAttachmentURLs(docKey);
		},

		/**
		* Revoke the attachment URL as required by the underlying
		* storage system.
		*
		* This is akin to `URL.revokeObjectURL(url)`
		* URLs that come from `getAttachmentURL` or `getAllAttachmentURLs` 
		* should be revoked by LLS and not `URL.revokeObjectURL`
		*
		* @example
		* 	storage.getAttachmentURL('doc', 'attach').then(function(url) {
		*		// do something with the URL
		*		storage.revokeAttachmentURL(url);
		* 	})
		*
		* @method revokeAttachmentURL
		* @param {string} url The URL as returned by `getAttachmentURL` or `getAttachmentURLs`
		* @returns {void}
		*/
		revokeAttachmentURL: function(url) {
			this._checkAvailability();
			return this._impl.revokeAttachmentURL(url);
		},

		/**
		* Remove an attachment from a document.
		*
		* @example
		* 	storage.rmAttachment('exampleDoc', 'someAttachment').then(function() {
		* 		alert('exampleDoc/someAttachment removed');
		* 	}).catch(function(e) {
		*		alert('Attachment removal failed: ' + e);
		* 	});
		*
		* @method rmAttachment
		* @param {string} docKey
		* @param {string} attachKey
		* @returns {promise} Promise that is resolved once the remove completes
		*/
		rmAttachment: function(docKey, attachKey) {
			if (!docKey) docKey = '__emptydoc__';
			this._checkAvailability();
			return this._impl.rmAttachment(docKey, attachKey);
		},

		/**
		* Returns the actual capacity of the storage or -1
		* if it is unknown.  If the user denies your request for
		* storage you'll get back some smaller amount of storage than what you
		* actually requested.
		*
		* TODO: return an estimated capacity if actual capacity is unknown?
		* -Firefox is 50MB until authorized to go above,
		* -Chrome is some % of available disk space,
		* -Safari unlimited as long as the user keeps authorizing size increases
		* -Opera same as safari?
		*
		* @example
		*	// the initialized property will call you back with the capacity
		* 	storage.initialized.then(function(capacity) {
		*		console.log('Authorized to store: ' + capacity + ' bytes');
		* 	});
		*	// or if you know your storage is already available
		*	// you can call getCapacity directly
		*	storage.getCapacity()
		*
		* @method getCapacity
		* @returns {number} Capacity, in bytes, of the storage.  -1 if unknown.
		*/
		getCapacity: function() {
			this._checkAvailability();
			if (this._impl.getCapacity)
				return this._impl.getCapacity();
			else
				return -1;
		},

		_checkAvailability: function() {
			if (!this._impl) {
				throw {
					msg: "No storage implementation is available yet.  The user most likely has not granted you app access to FileSystemAPI or IndexedDB",
					code: "NO_IMPLEMENTATION"
				};
			}
		}
	};

	LargeLocalStorage.contrib = {};

	function writeAttachments(docKey, attachments, storage) {
		var promises = [];
		attachments.forEach(function(attachment) {
			promises.push(storage.setAttachment(docKey, attachment.attachKey, attachment.data));
		});

		return Q.all(promises);
	}

	function copyDocs(docKeys, oldStorage, newStorage) {
		var promises = [];
		docKeys.forEach(function(key) {
			promises.push(oldStorage.getContents(key).then(function(contents) {
				return newStorage.setContents(key, contents);
			}));
		});

		docKeys.forEach(function(key) {
			promises.push(oldStorage.getAllAttachments(key).then(function(attachments) {
				return writeAttachments(key, attachments, newStorage);
			}));
		});

		return Q.all(promises);
	}

	LargeLocalStorage.copyOldData = function(err, oldStorage, newStorage, config) {
		if (err) {
			throw err;
		}

		oldStorage.ls().then(function(docKeys) {
			return copyDocs(docKeys, oldStorage, newStorage)
		}).then(function() {
			if (config.migrationComplete)
				config.migrationComplete();
		}, function(e) {
			config.migrationComplete(e);
		});
	};

	LargeLocalStorage._sessionMeta = sessionMeta;
	
	var availableProviders = [];
	Object.keys(providers).forEach(function(potentialProvider) {
		if (providers[potentialProvider].isAvailable())
			availableProviders.push(potentialProvider);
	});

	LargeLocalStorage.availableProviders = availableProviders;

	return LargeLocalStorage;
})(Q);

================================================
FILE: src/contrib/S3Link.js
================================================
LargeLocalStorage.contrib.S3Link = (function() {
	function S3Link(config) {

	}

	S3Link.prototype = {
		push: function(docKey, options) {

		}
	};

	return S3Link;
})();

================================================
FILE: src/contrib/URLCache.js
================================================
LargeLocalStorage.contrib.URLCache = (function() {
	var defaultOptions = {
		manageRevocation: true
	};

	function defaults(options, defaultOptions) {
		for (var k in defaultOptions) {
			if (options[k] === undefined)
				options[k] = defaultOptions[k];
		}

		return options;
	}

	function add(docKey, attachKey, url) {
		if (this.options.manageRevocation)
			expunge.call(this, docKey, attachKey, true);

		var mainCache = this.cache.main;
		var docCache = mainCache[docKey];
		if (!docCache) {
			docCache = {};
			mainCache[docKey] = docCache;
		}

		docCache[attachKey] = url;
		this.cache.reverse[url] = {docKey: docKey, attachKey: attachKey};
	}

	function addAll(urlEntries) {
		urlEntries.forEach(function(entry) {
			add.call(this, entry.docKey, entry.attachKey, entry.url);
		}, this);
	}

	function expunge(docKey, attachKey, needsRevoke) {
		function delAndRevoke(attachKey) {
			var url = docCache[attachKey];
			delete docCache[attachKey];
			delete this.cache.reverse[url];
			if (this.options.manageRevocation && needsRevoke)
				this.llshandler.revokeAttachmentURL(url, {bypassUrlCache: true});
		}

		var docCache = this.cache.main[docKey];
		if (docCache) {
			if (attachKey) {
				delAndRevoke.call(this, attachKey);
			} else {
				for (var attachKey in docCache) {
					delAndRevoke.call(this, attachKey);
				}
				delete this.cache.main[docKey];
			}
		}
	}

	function expungeByUrl(url) {
		var keys = this.cache.reverse[url];
		if (keys) {
			expunge.call(this, keys.docKey, keys.attachKey, false);
		}
	}


	function URLCache(llspipe, options) {
		options = options || {};
		this.options = defaults(options, defaultOptions);
		this.llshandler = llspipe.pipe.getHandler('lls');
		this.pending = {};
		this.cache = {
			main: {},
			reverse: {}
		};
	}

	URLCache.prototype = {
		setAttachment: function(docKey, attachKey, blob) {
			expunge.call(this, docKey, attachKey);
			return this.__pipectx.next(docKey, attachKey, blob);
		},

		rmAttachment: function(docKey, attachKey) {
			expunge.call(this, docKey, attachKey);
			return this.__pipectx.next(docKey, attachKey);
		},

		rm: function(docKey) {
			expunge.call(this, docKey);
			return this.__pipectx.next(docKey);
		},

		revokeAttachmentURL: function(url, options) {
			if (!options || !options.bypassUrlCache)
				expungeByUrl.call(this, url);

			return this.__pipectx.next(url, options);
		},

		getAttachmentURL: function(docKey, attachKey) {
			var pendingKey = docKey + attachKey;
			var pending = this.pending[pendingKey];
			if (pending)
				return pending;

			var promise = this.__pipectx.next(docKey, attachKey);
			var self = this;
			promise.then(function(url) {
				add.call(self, docKey, attachKey, url);
				delete self.pending[pendingKey];
			});

			this.pending[pendingKey] = promise;

			return promise;
		},

		// TODO: pending between this and getAttachmentURL...
		// Execute this as an ls and then
		// a loop on getAttachmentURL instead???
		// doing it the way mentiond above
		// will prevent us from leaking blobs.
		getAllAttachmentURLs: function(docKey) {
			var promise = this.__pipectx.next(docKey);
			var self = this;
			promise.then(function(urlEntries) {
				addAll.call(self, urlEntries);
			});

			return promise;
		},

		clear: function() {
			this.revokeAllCachedURLs();
			return this.__pipectx.next();
		},

		revokeAllCachedURLs: function() {
			for (var url in this.cache.reverse) {
				this.llshandler.revokeAttachmentURL(url, {bypassUrlCache: true});
			}

			this.cache.reverse = {};
			this.cache.main = {};
		}
	};

	return {
		addTo: function(lls, options) {
			var cache = new URLCache(lls, options);
			lls.pipe.addFirst('URLCache', cache);
			return lls;
		}
	}
})();

================================================
FILE: src/errors.js
================================================
define({
	
});

================================================
FILE: src/footer.js
================================================

	return LargeLocalStorage;
}

if (typeof define === 'function' && define.amd) {
	define(['Q'], definition);
} else {
	glob.LargeLocalStorage = definition.call(glob, Q);
}

}).call(this, this);

================================================
FILE: src/header.js
================================================
(function(glob) {
	var undefined = {}.a;

	function definition(Q) {
	

================================================
FILE: src/impls/FilesystemAPIProvider.js
================================================
var requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
var persistentStorage = navigator.persistentStorage || navigator.webkitPersistentStorage;
var FilesystemAPIProvider = (function(Q) {
	function makeErrorHandler(deferred, finalDeferred) {
		// TODO: normalize the error so
		// we can handle it upstream
		return function(e) {
			if (e.code == 1) {
				deferred.resolve(undefined);
			} else {
				if (finalDeferred)
					finalDeferred.reject(e);
				else
					deferred.reject(e);
			}
		}
	}

	function getAttachmentPath(docKey, attachKey) {
		docKey = docKey.replace(/\//g, '--');
		var attachmentsDir = docKey + "-attachments";
		return {
			dir: attachmentsDir,
			path: attachmentsDir + "/" + attachKey
		};
	}

	function readDirEntries(reader, result) {
		var deferred = Q.defer();

		_readDirEntries(reader, result, deferred);

		return deferred.promise;
	}

	function _readDirEntries(reader, result, deferred) {
		reader.readEntries(function(entries) {
			if (entries.length == 0) {
				deferred.resolve(result);
			} else {
				result = result.concat(entries);
				_readDirEntries(reader, result, deferred);
			}
		}, function(err) {
			deferred.reject(err);
		});
	}

	function entryToFile(entry, cb, eb) {
		entry.file(cb, eb);
	}

	function entryToURL(entry) {
		return entry.toURL();
	}

	function FSAPI(fs, numBytes, prefix) {
		this._fs = fs;
		this._capacity = numBytes;
		this._prefix = prefix;
		this.type = "FileSystemAPI";
	}

	FSAPI.prototype = {
		getContents: function(path, options) {
			var deferred = Q.defer();
			path = this._prefix + path;
			this._fs.root.getFile(path, {}, function(fileEntry) {
				fileEntry.file(function(file) {
					var reader = new FileReader();

					reader.onloadend = function(e) {
						var data = e.target.result;
						var err;
						if (options && options.json) {
							try {
								data = JSON.parse(data);
							} catch(e) {
								err = new Error('unable to parse JSON for ' + path);
							}
						}

						if (err) {
							deferred.reject(err);
						} else {
							deferred.resolve(data);
						}
					};

					reader.readAsText(file);
				}, makeErrorHandler(deferred));
			}, makeErrorHandler(deferred));

			return deferred.promise;
		},

		// create a file at path
		// and write `data` to it
		setContents: function(path, data, options) {
			var deferred = Q.defer();

			if (options && options.json)
				data = JSON.stringify(data);

			path = this._prefix + path;
			this._fs.root.getFile(path, {create:true}, function(fileEntry) {
				fileEntry.createWriter(function(fileWriter) {
					var blob;
					fileWriter.onwriteend = function(e) {
						fileWriter.onwriteend = function() {
							deferred.resolve();
						};
						fileWriter.truncate(blob.size);
					}

					fileWriter.onerror = makeErrorHandler(deferred);

					if (data instanceof Blob) {
						blob = data;
					} else {
						blob = new Blob([data], {type: 'text/plain'});
					}

					fileWriter.write(blob);
				}, makeErrorHandler(deferred));
			}, makeErrorHandler(deferred));

			return deferred.promise;
		},

		ls: function(docKey) {
			var isRoot = false;
			if (!docKey) {docKey = this._prefix; isRoot = true;}
			else docKey = this._prefix + docKey + "-attachments";

			var deferred = Q.defer();

			this._fs.root.getDirectory(docKey, {create:false},
			function(entry) {
				var reader = entry.createReader();
				readDirEntries(reader, []).then(function(entries) {
					var listing = [];
					entries.forEach(function(entry) {
						if (!entry.isDirectory) {
							listing.push(entry.name);
						}
					});
					deferred.resolve(listing);
				});
			}, function(error) {
				deferred.reject(error);
			});

			return deferred.promise;
		},

		clear: function() {
			var deferred = Q.defer();
			var failed = false;
			var ecb = function(err) {
				failed = true;
				deferred.reject(err);
			}

			this._fs.root.getDirectory(this._prefix, {},
			function(entry) {
				var reader = entry.createReader();
				reader.readEntries(function(entries) {
					var latch = 
					utils.countdown(entries.length, function() {
						if (!failed)
							deferred.resolve();
					});

					entries.forEach(function(entry) {
						if (entry.isDirectory) {
							entry.removeRecursively(latch, ecb);
						} else {
							entry.remove(latch, ecb);
						}
					});

					if (entries.length == 0)
						deferred.resolve();
				}, ecb);
			}, ecb);

			return deferred.promise;
		},

		rm: function(path) {
			var deferred = Q.defer();
			var finalDeferred = Q.defer();

			// remove attachments that go along with the path
			path = this._prefix + path;
			var attachmentsDir = path + "-attachments";

			this._fs.root.getFile(path, {create:false},
				function(entry) {
					entry.remove(function() {
						deferred.promise.then(finalDeferred.resolve);
					}, function(err) {
						finalDeferred.reject(err);
					});
				},
				makeErrorHandler(finalDeferred));

			this._fs.root.getDirectory(attachmentsDir, {},
				function(entry) {
					entry.removeRecursively(function() {
						deferred.resolve();
					}, function(err) {
						finalDeferred.reject(err);
					});
				},
				makeErrorHandler(deferred, finalDeferred));

			return finalDeferred.promise;
		},

		getAttachment: function(docKey, attachKey) {
			var attachmentPath = this._prefix + getAttachmentPath(docKey, attachKey).path;

			var deferred = Q.defer();
			this._fs.root.getFile(attachmentPath, {}, function(fileEntry) {
				fileEntry.file(function(file) {
					if (file.size == 0)
						deferred.resolve(undefined);
					else
						deferred.resolve(file);
				}, makeErrorHandler(deferred));
			}, function(err) {
				if (err.code == 1) {
					deferred.resolve(undefined);
				} else {
					deferred.reject(err);
				}
			});

			return deferred.promise;
		},

		getAttachmentURL: function(docKey, attachKey) {
			var attachmentPath = this._prefix + getAttachmentPath(docKey, attachKey).path;

			var deferred = Q.defer();
			var url = 'filesystem:' + window.location.protocol + '//' + window.location.host + '/persistent/' + attachmentPath;
			deferred.resolve(url);
			// this._fs.root.getFile(attachmentPath, {}, function(fileEntry) {
			// 	deferred.resolve(fileEntry.toURL());
			// }, makeErrorHandler(deferred, "getting attachment file entry"));

			return deferred.promise;
		},

		getAllAttachments: function(docKey) {
			var deferred = Q.defer();
			var attachmentsDir = this._prefix + docKey + "-attachments";

			this._fs.root.getDirectory(attachmentsDir, {},
			function(entry) {
				var reader = entry.createReader();
				deferred.resolve(
					utils.mapAsync(function(entry, cb, eb) {
						entry.file(function(file) {
							cb({
								data: file,
								docKey: docKey,
								attachKey: entry.name
							});
						}, eb);
					}, readDirEntries(reader, [])));
			}, function(err) {
				deferred.resolve([]);
			});

			return deferred.promise;
		},

		getAllAttachmentURLs: function(docKey) {
			var deferred = Q.defer();
			var attachmentsDir = this._prefix + docKey + "-attachments";

			this._fs.root.getDirectory(attachmentsDir, {},
			function(entry) {
				var reader = entry.createReader();
				readDirEntries(reader, []).then(function(entries) {
					deferred.resolve(entries.map(
					function(entry) {
						return {
							url: entry.toURL(),
							docKey: docKey,
							attachKey: entry.name
						};
					}));
				});
			}, function(err) {
				deferred.reject(err);
			});

			return deferred.promise;
		},

		revokeAttachmentURL: function(url) {
			// we return FS urls so this is a no-op
			// unless someone is being silly and doing
			// createObjectURL(getAttachment()) ......
		},

		// Create a folder at dirname(path)+"-attachments"
		// add attachment under that folder as basename(path)
		setAttachment: function(docKey, attachKey, data) {
			var attachInfo = getAttachmentPath(docKey, attachKey);

			var deferred = Q.defer();

			var self = this;
			this._fs.root.getDirectory(this._prefix + attachInfo.dir,
			{create:true}, function(dirEntry) {
				deferred.resolve(self.setContents(attachInfo.path, data));
			}, makeErrorHandler(deferred));

			return deferred.promise;
		},

		// rm the thing at dirname(path)+"-attachments/"+basename(path)
		rmAttachment: function(docKey, attachKey) {
			var attachmentPath = getAttachmentPath(docKey, attachKey).path;

			var deferred = Q.defer();
			this._fs.root.getFile(this._prefix + attachmentPath, {create:false},
				function(entry) {
					entry.remove(function() {
						deferred.resolve();
					}, makeErrorHandler(deferred));
			}, makeErrorHandler(deferred));

			return deferred.promise;
		},

		getCapacity: function() {
			return this._capacity;
		}
	};

	return {
		init: function(config) {
			var deferred = Q.defer();

			if (!requestFileSystem) {
				deferred.reject("No FS API");
				return deferred.promise;
			}

			var prefix = config.name + '/';

			persistentStorage.requestQuota(config.size,
			function(numBytes) {
				requestFileSystem(window.PERSISTENT, numBytes,
				function(fs) {
					fs.root.getDirectory(config.name, {create: true},
					function() {
						deferred.resolve(new FSAPI(fs, numBytes, prefix));
					}, function(err) {
						console.error(err);
						deferred.reject(err);
					});
				}, function(err) {
					// TODO: implement various error messages.
					console.error(err);
					deferred.reject(err);
				});
			}, function(err) {
				// TODO: implement various error messages.
				console.error(err);
				deferred.reject(err);
			});

			return deferred.promise;
		},

		isAvailable: function() {
			return requestFileSystem != null;
		}
	}
})(Q);

================================================
FILE: src/impls/IndexedDBProvider.js
================================================
var indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.OIndexedDB || window.msIndexedDB;
var IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.OIDBTransaction || window.msIDBTransaction;
var IndexedDBProvider = (function(Q) {
	var URL = window.URL || window.webkitURL;

	var convertToBase64 = utils.convertToBase64;
	var dataURLToBlob = utils.dataURLToBlob;

	function IDB(db) {
		this._db = db;
		this.type = 'IndexedDB';

		var transaction = this._db.transaction(['attachments'], 'readwrite');
		this._supportsBlobs = true;
		try {
			transaction.objectStore('attachments')
			.put(Blob(["sdf"], {type: "text/plain"}), "featurecheck");
		} catch (e) {
			this._supportsBlobs = false;
		}
	}

	// TODO: normalize returns and errors.
	IDB.prototype = {
		getContents: function(docKey) {
			var deferred = Q.defer();
			var transaction = this._db.transaction(['files'], 'readonly');

			var get = transaction.objectStore('files').get(docKey);
			get.onsuccess = function(e) {
				deferred.resolve(e.target.result);
			};

			get.onerror = function(e) {
				deferred.reject(e);
			};

			return deferred.promise;
		},

		setContents: function(docKey, data) {
			var deferred = Q.defer();
			var transaction = this._db.transaction(['files'], 'readwrite');

			var put = transaction.objectStore('files').put(data, docKey);
			put.onsuccess = function(e) {
				deferred.resolve(e);
			};

			put.onerror = function(e) {
				deferred.reject(e);
			};

			return deferred.promise;
		},

		rm: function(docKey) {
			var deferred = Q.defer();
			var finalDeferred = Q.defer();

			var transaction = this._db.transaction(['files', 'attachments'], 'readwrite');
			
			var del = transaction.objectStore('files').delete(docKey);

			del.onsuccess = function(e) {
				deferred.promise.then(function() {
					finalDeferred.resolve();
				});
			};

			del.onerror = function(e) {
				deferred.promise.catch(function() {
					finalDeferred.reject(e);
				});
			};

			var attachmentsStore = transaction.objectStore('attachments');
			var index = attachmentsStore.index('fname');
			var cursor = index.openCursor(IDBKeyRange.only(docKey));
			cursor.onsuccess = function(e) {
				var cursor = e.target.result;
				if (cursor) {
					cursor.delete();
					cursor.continue();
				} else {
					deferred.resolve();
				}
			};

			cursor.onerror = function(e) {
				deferred.reject(e);
			}

			return finalDeferred.promise;
		},

		getAttachment: function(docKey, attachKey) {
			var deferred = Q.defer();

			var transaction = this._db.transaction(['attachments'], 'readonly');
			var get = transaction.objectStore('attachments').get(docKey + '/' + attachKey);

			var self = this;
			get.onsuccess = function(e) {
				if (!e.target.result) {
					deferred.resolve(undefined);
					return;
				}

				var data = e.target.result.data;
				if (!self._supportsBlobs) {
					data = dataURLToBlob(data);
				}
				deferred.resolve(data);
			};

			get.onerror = function(e) {
				deferred.reject(e);
			};

			return deferred.promise;
		},

		ls: function(docKey) {
			var deferred = Q.defer();

			if (!docKey) {
				// list docs
				var store = 'files';
			} else {
				// list attachments
				var store = 'attachments';
			}

			var transaction = this._db.transaction([store], 'readonly');
			var cursor = transaction.objectStore(store).openCursor();
			var listing = [];

			cursor.onsuccess = function(e) {
				var cursor = e.target.result;
				if (cursor) {
					listing.push(!docKey ? cursor.key : cursor.key.split('/')[1]);
					cursor.continue();
				} else {
					deferred.resolve(listing);
				}
			};

			cursor.onerror = function(e) {
				deferred.reject(e);
			};

			return deferred.promise;
		},

		clear: function() {
			var deferred = Q.defer();
			var finalDeferred = Q.defer();

			var t = this._db.transaction(['attachments', 'files'], 'readwrite');


			var req1 = t.objectStore('attachments').clear();
			var req2 = t.objectStore('files').clear();

			req1.onsuccess = function() {
				deferred.promise.then(finalDeferred.resolve);
			};

			req2.onsuccess = function() {
				deferred.resolve();
			};

			req1.onerror = function(err) {
				finalDeferred.reject(err);
			};

			req2.onerror = function(err) {
				finalDeferred.reject(err);
			};

			return finalDeferred.promise;
		},

		getAllAttachments: function(docKey) {
			var deferred = Q.defer();
			var self = this;

			var transaction = this._db.transaction(['attachments'], 'readonly');
			var index = transaction.objectStore('attachments').index('fname');

			var cursor = index.openCursor(IDBKeyRange.only(docKey));
			var values = [];
			cursor.onsuccess = function(e) {
				var cursor = e.target.result;
				if (cursor) {
					var data;
					if (!self._supportsBlobs) {
						data = dataURLToBlob(cursor.value.data)
					} else {
						data = cursor.value.data;
					}
					values.push({
						data: data,
						docKey: docKey,
						attachKey: cursor.primaryKey.split('/')[1] // TODO
					});
					cursor.continue();
				} else {
					deferred.resolve(values);
				}
			};

			cursor.onerror = function(e) {
				deferred.reject(e);
			};

			return deferred.promise;
		},

		getAllAttachmentURLs: function(docKey) {
			var deferred = Q.defer();
			this.getAllAttachments(docKey).then(function(attachments) {
				var urls = attachments.map(function(a) {
					a.url = URL.createObjectURL(a.data);
					delete a.data;
					return a;
				});

				deferred.resolve(urls);
			}, function(e) {
				deferred.reject(e);
			});

			return deferred.promise;
		},

		getAttachmentURL: function(docKey, attachKey) {
			var deferred = Q.defer();
			this.getAttachment(docKey, attachKey).then(function(attachment) {
				deferred.resolve(URL.createObjectURL(attachment));
			}, function(e) {
				deferred.reject(e);
			});

			return deferred.promise;
		},

		revokeAttachmentURL: function(url) {
			URL.revokeObjectURL(url);
		},

		setAttachment: function(docKey, attachKey, data) {
			var deferred = Q.defer();

			if (data instanceof Blob && !this._supportsBlobs) {
				var self = this;
				convertToBase64(data, function(data) {
					continuation.call(self, data);
				});
			} else {
				continuation.call(this, data);
			}

			function continuation(data) {
				var obj = {
					path: docKey + '/' + attachKey,
					fname: docKey,
					data: data
				};
				var transaction = this._db.transaction(['attachments'], 'readwrite');
				var put = transaction.objectStore('attachments').put(obj);

				put.onsuccess = function(e) {
					deferred.resolve(e);
				};

				put.onerror = function(e) {
					deferred.reject(e);
				};
			}

			return deferred.promise;
		},

		rmAttachment: function(docKey, attachKey) {
			var deferred = Q.defer();
			var transaction = this._db.transaction(['attachments'], 'readwrite');
			var del = transaction.objectStore('attachments').delete(docKey + '/' + attachKey);

			del.onsuccess = function(e) {
				deferred.resolve(e);
			};

			del.onerror = function(e) {
				deferred.reject(e);
			};

			return deferred.promise;
		}
	};

	return {
		init: function(config) {
			var deferred = Q.defer();
			var dbVersion = 2;

			if (!indexedDB || !IDBTransaction) {
				deferred.reject("No IndexedDB");
				return deferred.promise;
			}

			var request = indexedDB.open(config.name, dbVersion);

			function createObjectStore(db) {
				db.createObjectStore("files");
				var attachStore = db.createObjectStore("attachments", {keyPath: 'path'});
				attachStore.createIndex('fname', 'fname', {unique: false})
			}

			// TODO: normalize errors
			request.onerror = function (event) {
				deferred.reject(event);
			};
		 
			request.onsuccess = function (event) {
				var db = request.result;
		 
				db.onerror = function (event) {
					console.log(event);
				};
				
				// Chrome workaround
				if (db.setVersion) {
					if (db.version != dbVersion) {
						var setVersion = db.setVersion(dbVersion);
						setVersion.onsuccess = function () {
							createObjectStore(db);
							deferred.resolve();
						};
					}
					else {
						deferred.resolve(new IDB(db));
					}
				} else {
					deferred.resolve(new IDB(db));
				}
			}
			
			request.onupgradeneeded = function (event) {
				createObjectStore(event.target.result);
			};

			return deferred.promise;
		},

		isAvailable: function() {
			return indexedDB != null && IDBTransaction != null;
		}
	}
})(Q);

================================================
FILE: src/impls/LocalStorageProvider.js
================================================
var LocalStorageProvider = (function(Q) {
	return {
		init: function() {
			return Q({type: 'LocalStorage'});
		}
	}
})(Q);

================================================
FILE: src/impls/WebSQLProvider.js
================================================
var openDb = window.openDatabase;
var WebSQLProvider = (function(Q) {
	var URL = window.URL || window.webkitURL;
	var convertToBase64 = utils.convertToBase64;
	var dataURLToBlob = utils.dataURLToBlob;

	function WSQL(db) {
		this._db = db;
		this.type = 'WebSQL';
	}

	WSQL.prototype = {
		getContents: function(docKey, options) {
			var deferred = Q.defer();
			this._db.transaction(function(tx) {
				tx.executeSql('SELECT value FROM files WHERE fname = ?', [docKey],
				function(tx, res) {
					if (res.rows.length == 0) {
						deferred.resolve(undefined);
					} else {
						var data = res.rows.item(0).value;
						if (options && options.json)
							data = JSON.parse(data);
						deferred.resolve(data);
					}
				});
			}, function(err) {
				consol.log(err);
				deferred.reject(err);
			});

			return deferred.promise;
		},

		setContents: function(docKey, data, options) {
			var deferred = Q.defer();
			if (options && options.json)
				data = JSON.stringify(data);

			this._db.transaction(function(tx) {
				tx.executeSql(
				'INSERT OR REPLACE INTO files (fname, value) VALUES(?, ?)', [docKey, data]);
			}, function(err) {
				console.log(err);
				deferred.reject(err);
			}, function() {
				deferred.resolve();
			});

			return deferred.promise;
		},

		rm: function(docKey) {
			var deferred = Q.defer();

			this._db.transaction(function(tx) {
				tx.executeSql('DELETE FROM files WHERE fname = ?', [docKey]);
				tx.executeSql('DELETE FROM attachments WHERE fname = ?', [docKey]);
			}, function(err) {
				console.log(err);
				deferred.reject(err);
			}, function() {
				deferred.resolve();
			});

			return deferred.promise;
		},

		getAttachment: function(fname, akey) {
			var deferred = Q.defer();

			this._db.transaction(function(tx){ 
				tx.executeSql('SELECT value FROM attachments WHERE fname = ? AND akey = ?',
				[fname, akey],
				function(tx, res) {
					if (res.rows.length == 0) {
						deferred.resolve(undefined);
					} else {
						deferred.resolve(dataURLToBlob(res.rows.item(0).value));
					}
				});
			}, function(err) {
				deferred.reject(err);
			});

			return deferred.promise;
		},

		getAttachmentURL: function(docKey, attachKey) {
			var deferred = Q.defer();
			this.getAttachment(docKey, attachKey).then(function(blob) {
				deferred.resolve(URL.createObjectURL(blob));
			}, function() {
				deferred.reject();
			});

			return deferred.promise;
		},

		ls: function(docKey) {
			var deferred = Q.defer();

			var select;
			var field;
			if (!docKey) {
				select = 'SELECT fname FROM files';
				field = 'fname';
			} else {
				select = 'SELECT akey FROM attachments WHERE fname = ?';
				field = 'akey';
			}

			this._db.transaction(function(tx) {
				tx.executeSql(select, docKey ? [docKey] : [],
				function(tx, res) {
					var listing = [];
					for (var i = 0; i < res.rows.length; ++i) {
						listing.push(res.rows.item(i)[field]);
					}

					deferred.resolve(listing);
				}, function(err) {
					deferred.reject(err);
				});
			});

			return deferred.promise;
		},

		clear: function() {
			var deffered1 = Q.defer();
			var deffered2 = Q.defer();

			this._db.transaction(function(tx) {
				tx.executeSql('DELETE FROM files', function() {
					deffered1.resolve();
				});
				tx.executeSql('DELETE FROM attachments', function() {
					deffered2.resolve();
				});
			}, function(err) {
				deffered1.reject(err);
				deffered2.reject(err);
			});

			return Q.all([deffered1, deffered2]);
		},

		getAllAttachments: function(fname) {
			var deferred = Q.defer();

			this._db.transaction(function(tx) {
				tx.executeSql('SELECT value, akey FROM attachments WHERE fname = ?',
				[fname],
				function(tx, res) {
					// TODO: ship this work off to a webworker
					// since there could be many of these conversions?
					var result = [];
					for (var i = 0; i < res.rows.length; ++i) {
						var item = res.rows.item(i);
						result.push({
							docKey: fname,
							attachKey: item.akey,
							data: dataURLToBlob(item.value)
						});
					}

					deferred.resolve(result);
				});
			}, function(err) {
				deferred.reject(err);
			});

			return deferred.promise;
		},

		getAllAttachmentURLs: function(fname) {
			var deferred = Q.defer();
			this.getAllAttachments(fname).then(function(attachments) {
				var urls = attachments.map(function(a) {
					a.url = URL.createObjectURL(a.data);
					delete a.data;
					return a;
				});

				deferred.resolve(urls);
			}, function(e) {
				deferred.reject(e);
			});

			return deferred.promise;
		},

		revokeAttachmentURL: function(url) {
			URL.revokeObjectURL(url);
		},

		setAttachment: function(fname, akey, data) {
			var deferred = Q.defer();

			var self = this;
			convertToBase64(data, function(data) {
				self._db.transaction(function(tx) {
					tx.executeSql(
					'INSERT OR REPLACE INTO attachments (fname, akey, value) VALUES(?, ?, ?)',
					[fname, akey, data]);
				}, function(err) {
					deferred.reject(err);
				}, function() {
					deferred.resolve();
				});
			});

			return deferred.promise;
		},

		rmAttachment: function(fname, akey) {
			var deferred = Q.defer();
			this._db.transaction(function(tx) {
				tx.executeSql('DELETE FROM attachments WHERE fname = ? AND akey = ?',
				[fname, akey]);
			}, function(err) {
				deferred.reject(err);
			}, function() {
				deferred.resolve();
			});

			return deferred.promise;
		}
	};

	return {
		init: function(config) {
			var deferred = Q.defer();
			if (!openDb) {
				deferred.reject("No WebSQL");
				return deferred.promise;
			}

			var db = openDb(config.name, '1.0', 'large local storage', config.size);

			db.transaction(function(tx) {
				tx.executeSql('CREATE TABLE IF NOT EXISTS files (fname unique, value)');
				tx.executeSql('CREATE TABLE IF NOT EXISTS attachments (fname, akey, value)');
				tx.executeSql('CREATE INDEX IF NOT EXISTS fname_index ON attachments (fname)');
				tx.executeSql('CREATE INDEX IF NOT EXISTS akey_index ON attachments (akey)');
				tx.executeSql('CREATE UNIQUE INDEX IF NOT EXISTS uniq_attach ON attachments (fname, akey)')
			}, function(err) {
				deferred.reject(err);
			}, function() {
				deferred.resolve(new WSQL(db));
			});

			return deferred.promise;
		},

		isAvailable: function() {
			return openDb != null;
		}
	}
})(Q);

================================================
FILE: src/impls/utils.js
================================================
var utils = (function() {
	return {
		convertToBase64: function(blob, cb) {
			var fr = new FileReader();
			fr.onload = function(e) {
				cb(e.target.result);
			};
			fr.onerror = function(e) {
			};
			fr.onabort = function(e) {
			};
			fr.readAsDataURL(blob);
		},

		dataURLToBlob: function(dataURL) {
				var BASE64_MARKER = ';base64,';
				if (dataURL.indexOf(BASE64_MARKER) == -1) {
					var parts = dataURL.split(',');
					var contentType = parts[0].split(':')[1];
					var raw = parts[1];

					return new Blob([raw], {type: contentType});
				}

				var parts = dataURL.split(BASE64_MARKER);
				var contentType = parts[0].split(':')[1];
				var raw = window.atob(parts[1]);
				var rawLength = raw.length;

				var uInt8Array = new Uint8Array(rawLength);

				for (var i = 0; i < rawLength; ++i) {
					uInt8Array[i] = raw.charCodeAt(i);
				}

				return new Blob([uInt8Array.buffer], {type: contentType});
		},

		splitAttachmentPath: function(path) {
			var parts = path.split('/');
			if (parts.length == 1) 
				parts.unshift('__nodoc__');
			return parts;
		},

		mapAsync: function(fn, promise) {
			var deferred = Q.defer();
			promise.then(function(data) {
				_mapAsync(fn, data, [], deferred);
			}, function(e) {
				deferred.reject(e);
			});

			return deferred.promise;
		},

		countdown: function(n, cb) {
		    var args = [];
		    return function() {
		      for (var i = 0; i < arguments.length; ++i)
		        args.push(arguments[i]);
		      n -= 1;
		      if (n == 0)
		        cb.apply(this, args);
		    }
		}
	};

	function _mapAsync(fn, data, result, deferred) {
		fn(data[result.length], function(v) {
			result.push(v);
			if (result.length == data.length)
				deferred.resolve(result);
			else
				_mapAsync(fn, data, result, deferred);
		}, function(err) {
			deferred.reject(err);
		})
	}
})();

================================================
FILE: src/pipeline.js
================================================

/**
@author Matt Crinklaw-Vogt
*/
function PipeContext(handlers, nextMehod, end) {
	this._handlers = handlers;
	this._next = nextMehod;
	this._end = end;

	this._i = 0;
}

PipeContext.prototype = {
	next: function() {
		// var args = Array.prototype.slice.call(arguments, 0);
		// args.unshift(this);
		this.__pipectx = this;
		return this._next.apply(this, arguments);
	},

	_nextHandler: function() {
		if (this._i >= this._handlers.length) return this._end;

		var handler = this._handlers[this._i].handler;
		this._i += 1;
		return handler;
	},

	length: function() {
		return this._handlers.length;
	}
};

function indexOfHandler(handlers, len, target) {
	for (var i = 0; i < len; ++i) {
		var handler = handlers[i];
		if (handler.name === target || handler.handler === target) {
			return i;
		}
	}

	return -1;
}

function forward(ctx) {
	return ctx.next.apply(ctx, Array.prototype.slice.call(arguments, 1));
}

function coerce(methodNames, handler) {
	methodNames.forEach(function(meth) {
		if (!handler[meth])
			handler[meth] = forward;
	});
}

var abstractPipeline = {
	addFirst: function(name, handler) {
		coerce(this._pipedMethodNames, handler);
		this._handlers.unshift({name: name, handler: handler});
	},

	addLast: function(name, handler) {
		coerce(this._pipedMethodNames, handler);
		this._handlers.push({name: name, handler: handler});
	},

 	/**
 	Add the handler with the given name after the 
 	handler specified by target.  Target can be a handler
 	name or a handler instance.
 	*/
	addAfter: function(target, name, handler) {
		coerce(this._pipedMethodNames, handler);
		var handlers = this._handlers;
		var len = handlers.length;
		var i = indexOfHandler(handlers, len, target);

		if (i >= 0) {
			handlers.splice(i+1, 0, {name: name, handler: handler});
		}
	},

	/**
	Add the handler with the given name after the handler
	specified by target.  Target can be a handler name or
	a handler instance.
	*/
	addBefore: function(target, name, handler) {
		coerce(this._pipedMethodNames, handler);
		var handlers = this._handlers;
		var len = handlers.length;
		var i = indexOfHandler(handlers, len, target);

		if (i >= 0) {
			handlers.splice(i, 0, {name: name, handler: handler});
		}
	},

	/**
	Replace the handler specified by target.
	*/
	replace: function(target, newName, handler) {
		coerce(this._pipedMethodNames, handler);
		var handlers = this._handlers;
		var len = handlers.length;
		var i = indexOfHandler(handlers, len, target);

		if (i >= 0) {
			handlers.splice(i, 1, {name: newName, handler: handler});
		}
	},

	removeFirst: function() {
		return this._handlers.shift();
	},

	removeLast: function() {
		return this._handlers.pop();
	},

	remove: function(target) {
		var handlers = this._handlers;
		var len = handlers.length;
		var i = indexOfHandler(handlers, len, target);

		if (i >= 0)
			handlers.splice(i, 1);
	},

	getHandler: function(name) {
		var i = indexOfHandler(this._handlers, this._handlers.length, name);
		if (i >= 0)
			return this._handlers[i].handler;
		return null;
	}
};

function createPipeline(pipedMethodNames) {
	var end = {};
	var endStubFunc = function() { return end; };
	var nextMethods = {};

	function Pipeline(pipedMethodNames) {
		this.pipe = {
			_handlers: [],
			_contextCtor: PipeContext,
			_nextMethods: nextMethods,
			end: end,
			_pipedMethodNames: pipedMethodNames
		};
	}

	var pipeline = new Pipeline(pipedMethodNames);
	for (var k in abstractPipeline) {
		pipeline.pipe[k] = abstractPipeline[k];
	}

	pipedMethodNames.forEach(function(name) {
		end[name] = endStubFunc;

		nextMethods[name] = new Function(
			"var handler = this._nextHandler();" +
			"handler.__pipectx = this.__pipectx;" +
			"return handler." + name + ".apply(handler, arguments);");

		pipeline[name] = new Function(
			"var ctx = new this.pipe._contextCtor(this.pipe._handlers, this.pipe._nextMethods." + name + ", this.pipe.end);"
			+ "return ctx.next.apply(ctx, arguments);");
	});

	return pipeline;
}

createPipeline.isPipeline = function(obj) {
	return obj instanceof Pipeline;
}

================================================
FILE: test/index.html
================================================
<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <title>Mocha Spec Runner</title>
  <link rel="stylesheet" href="lib/mocha/mocha.css">
</head>
<body>
  <div id="mocha"></div>
  <script>window.isOptimized = true;</script>
  <script src="lib/mocha/mocha.js"></script>
  <!-- define globals for mocha -->
  <script>mocha.setup({ ui: 'bdd', globals: ['_', '$', 'jQuery*', 'Backbone', 'JST', 'browserPrefix'] })</script>
  <!-- assertion framework -->
  <script src="lib/chai.js"></script>
  <script>expect = chai.expect</script>
  <script src="lib/sinon.js"></script>
  <script>
  function countdown(n, cb) {
    var args = [];
    return function() {
      for (var i = 0; i < arguments.length; ++i)
        args.push(arguments[i]);
      n -= 1;
      if (n == 0)
        cb.apply(this, args);
    }
  }
  window.runMocha = countdown(2, function() {
      var runner = mocha.run();
  });
  </script>
  <script src="../bower_components/jquery/jquery.js"></script>
  <script src="../bower_components/q/q.js"></script>
  <script src="../dist/LargeLocalStorage.js"></script>
  <script src="../dist/contrib/URLCache.js"></script>
  <script src="spec/LargeLocalStorageTest.js"></script>
  <script src="spec/URLCacheTest.js"></script>
</body>
</html>


================================================
FILE: test/lib/chai.js
================================================
!function (name, context, definition) {
  if (typeof require === 'function' && typeof exports === 'object' && typeof module === 'object') {
    module.exports = definition();
  } else if (typeof define === 'function' && typeof define.amd  === 'object') {
    define(function () {
      return definition();
    });
  } else {
    context[name] = definition();
  }
}('chai', this, function () {

  function require(p) {
    var path = require.resolve(p)
      , mod = require.modules[path];
    if (!mod) throw new Error('failed to require "' + p + '"');
    if (!mod.exports) {
      mod.exports = {};
      mod.call(mod.exports, mod, mod.exports, require.relative(path));
    }
    return mod.exports;
  }

  require.modules = {};

  require.resolve = function (path) {
    var orig = path
      , reg = path + '.js'
      , index = path + '/index.js';
    return require.modules[reg] && reg
      || require.modules[index] && index
      || orig;
  };

  require.register = function (path, fn) {
    require.modules[path] = fn;
  };

  require.relative = function (parent) {
    return function(p){
      if ('.' != p[0]) return require(p);

      var path = parent.split('/')
        , segs = p.split('/');
      path.pop();

      for (var i = 0; i < segs.length; i++) {
        var seg = segs[i];
        if ('..' == seg) path.pop();
        else if ('.' != seg) path.push(seg);
      }

      return require(path.join('/'));
    };
  };

  require.alias = function (from, to) {
    var fn = require.modules[from];
    require.modules[to] = fn;
  };


  require.register("chai.js", function(module, exports, require){
    /*!
     * chai
     * Copyright(c) 2011-2012 Jake Luer <jake@alogicalparadox.com>
     * MIT Licensed
     */

    var used = []
      , exports = module.exports = {};

    /*!
     * Chai version
     */

    exports.version = '1.2.0';

    /*!
     * Primary `Assertion` prototype
     */

    exports.Assertion = require('./chai/assertion');

    /*!
     * Assertion Error
     */

    exports.AssertionError = require('./chai/browser/error');

    /*!
     * Utils for plugins (not exported)
     */

    var util = require('./chai/utils');

    /**
     * # .use(function)
     *
     * Provides a way to extend the internals of Chai
     *
     * @param {Function}
     * @returns {this} for chaining
     * @api public
     */

    exports.use = function (fn) {
      if (!~used.indexOf(fn)) {
        fn(this, util);
        used.push(fn);
      }

      return this;
    };

    /*!
     * Core Assertions
     */

    var core = require('./chai/core/assertions');
    exports.use(core);

    /*!
     * Expect interface
     */

    var expect = require('./chai/interface/expect');
    exports.use(expect);

    /*!
     * Should interface
     */

    var should = require('./chai/interface/should');
    exports.use(should);

    /*!
     * Assert interface
     */

    var assert = require('./chai/interface/assert');
    exports.use(assert);

  }); // module: chai.js

  require.register("chai/assertion.js", function(module, exports, require){
    /*!
     * chai
     * http://chaijs.com
     * Copyright(c) 2011-2012 Jake Luer <jake@alogicalparadox.com>
     * MIT Licensed
     */

    /*!
     * Module dependencies.
     */

    var AssertionError = require('./browser/error')
      , util = require('./utils')
      , flag = util.flag;

    /*!
     * Module export.
     */

    module.exports = Assertion;


    /*!
     * Assertion Constructor
     *
     * Creates object for chaining.
     *
     * @api private
     */

    function Assertion (obj, msg, stack) {
      flag(this, 'ssfi', stack || arguments.callee);
      flag(this, 'object', obj);
      flag(this, 'message', msg);
    }

    /*!
      * ### Assertion.includeStack
      *
      * User configurable property, influences whether stack trace
      * is included in Assertion error message. Default of false
      * suppresses stack trace in the error message
      *
      *     Assertion.includeStack = true;  // enable stack on error
      *
      * @api public
      */

    Assertion.includeStack = false;

    Assertion.addProperty = function (name, fn) {
      util.addProperty(this.prototype, name, fn);
    };

    Assertion.addMethod = function (name, fn) {
      util.addMethod(this.prototype, name, fn);
    };

    Assertion.addChainableMethod = function (name, fn, chainingBehavior) {
      util.addChainableMethod(this.prototype, name, fn, chainingBehavior);
    };

    Assertion.overwriteProperty = function (name, fn) {
      util.overwriteProperty(this.prototype, name, fn);
    };

    Assertion.overwriteMethod = function (name, fn) {
      util.overwriteMethod(this.prototype, name, fn);
    };

    /*!
     * ### .assert(expression, message, negateMessage, expected, actual)
     *
     * Executes an expression and check expectations. Throws AssertionError for reporting if test doesn't pass.
     *
     * @name assert
     * @param {Philosophical} expression to be tested
     * @param {String} message to display if fails
     * @param {String} negatedMessage to display if negated expression fails
     * @param {Mixed} expected value (remember to check for negation)
     * @param {Mixed} actual (optional) will default to `this.obj`
     * @api private
     */

    Assertion.prototype.assert = function (expr, msg, negateMsg, expected, _actual) {
      var ok = util.test(this, arguments);

      if (!ok) {
        var msg = util.getMessage(this, arguments)
          , actual = util.getActual(this, arguments);
        throw new AssertionError({
            message: msg
          , actual: actual
          , expected: expected
          , stackStartFunction: (Assertion.includeStack) ? this.assert : flag(this, 'ssfi')
        });
      }
    };

    /*!
     * ### ._obj
     *
     * Quick reference to stored `actual` value for plugin developers.
     *
     * @api private
     */

    Object.defineProperty(Assertion.prototype, '_obj',
      { get: function () {
          return flag(this, 'object');
        }
      , set: function (val) {
          flag(this, 'object', val);
        }
    });

  }); // module: chai/assertion.js

  require.register("chai/browser/error.js", function(module, exports, require){
    /*!
     * chai
     * Copyright(c) 2011-2012 Jake Luer <jake@alogicalparadox.com>
     * MIT Licensed
     */

    module.exports = AssertionError;

    function AssertionError (options) {
      options = options || {};
      this.message = options.message;
      this.actual = options.actual;
      this.expected = options.expected;
      this.operator = options.operator;

      if (options.stackStartFunction && Error.captureStackTrace) {
        var stackStartFunction = options.stackStartFunction;
        Error.captureStackTrace(this, stackStartFunction);
      }
    }

    AssertionError.prototype = Object.create(Error.prototype);
    AssertionError.prototype.name = 'AssertionError';
    AssertionError.prototype.constructor = AssertionError;

    AssertionError.prototype.toString = function() {
      return this.message;
    };

  }); // module: chai/browser/error.js

  require.register("chai/core/assertions.js", function(module, exports, require){
    /*!
     * chai
     * http://chaijs.com
     * Copyright(c) 2011-2012 Jake Luer <jake@alogicalparadox.com>
     * MIT Licensed
     */

    module.exports = function (chai, _) {
      var Assertion = chai.Assertion
        , toString = Object.prototype.toString
        , flag = _.flag;

      /**
       * ### Language Chains
       *
       * The following are provide as chainable getters to
       * improve the readability of your assertions. They
       * do not provide an testing capability unless they
       * have been overwritten by a plugin.
       *
       * **Chains**
       *
       * - to
       * - be
       * - been
       * - is
       * - that
       * - and
       * - have
       * - with
       *
       * @name language chains
       * @api public
       */

      [ 'to', 'be', 'been'
      , 'is', 'and', 'have'
      , 'with', 'that' ].forEach(function (chain) {
        Assertion.addProperty(chain, function () {
          return this;
        });
      });

      /**
       * ### .not
       *
       * Negates any of assertions following in the chain.
       *
       *     expect(foo).to.not.equal('bar');
       *     expect(goodFn).to.not.throw(Error);
       *     expect({ foo: 'baz' }).to.have.property('foo')
       *       .and.not.equal('bar');
       *
       * @name not
       * @api public
       */

      Assertion.addProperty('not', function () {
        flag(this, 'negate', true);
      });

      /**
       * ### .deep
       *
       * Sets the `deep` flag, later used by the `equal` and
       * `property` assertions.
       *
       *     expect(foo).to.deep.equal({ bar: 'baz' });
       *     expect({ foo: { bar: { baz: 'quux' } } })
       *       .to.have.deep.property('foo.bar.baz', 'quux');
       *
       * @name deep
       * @api public
       */

      Assertion.addProperty('deep', function () {
        flag(this, 'deep', true);
      });

      /**
       * ### .a(type)
       *
       * The `a` and `an` assertions are aliases that can be
       * used either as language chains or to assert a value's
       * type (as revealed by `Object.prototype.toString`).
       *
       *     // typeof
       *     expect('test').to.be.a('string');
       *     expect({ foo: 'bar' }).to.be.an('object');
       *     expect(null).to.be.a('null');
       *     expect(undefined).to.be.an('undefined');
       *
       *     // language chain
       *     expect(foo).to.be.an.instanceof(Foo);
       *
       * @name a
       * @alias an
       * @param {String} type
       * @api public
       */

      function an(type) {
        var obj = flag(this, 'object')
          , klassStart = type.charAt(0).toUpperCase()
          , klass = klassStart + type.slice(1)
          , article = ~[ 'A', 'E', 'I', 'O', 'U' ].indexOf(klassStart) ? 'an ' : 'a ';

        this.assert(
            '[object ' + klass + ']' === toString.call(obj)
          , 'expected #{this} to be ' + article + type
          , 'expected #{this} not to be ' + article + type
        );
      }

      Assertion.addChainableMethod('an', an);
      Assertion.addChainableMethod('a', an);

      /**
       * ### .include(value)
       *
       * The `include` and `contain` assertions can be used as either property
       * based language chains or as methods to assert the inclusion of an object
       * in an array or a substring in a string. When used as language chains,
       * they toggle the `contain` flag for the `keys` assertion.
       *
       *     expect([1,2,3]).to.include(2);
       *     expect('foobar').to.contain('foo');
       *     expect({ foo: 'bar', hello: 'universe' }).to.include.keys('foo');
       *
       * @name include
       * @alias contain
       * @param {Object|String|Number} obj
       * @api public
       */

      function includeChainingBehavior () {
        flag(this, 'contains', true);
      }

      function include (val) {
        var obj = flag(this, 'object')
        this.assert(
            ~obj.indexOf(val)
          , 'expected #{this} to include ' + _.inspect(val)
          , 'expected #{this} to not include ' + _.inspect(val));
      }

      Assertion.addChainableMethod('include', include, includeChainingBehavior);
      Assertion.addChainableMethod('contain', include, includeChainingBehavior);

      /**
       * ### .ok
       *
       * Asserts that the target is truthy.
       *
       *     expect('everthing').to.be.ok;
       *     expect(1).to.be.ok;
       *     expect(false).to.not.be.ok;
       *     expect(undefined).to.not.be.ok;
       *     expect(null).to.not.be.ok;
       *
       * @name ok
       * @api public
       */

      Assertion.addProperty('ok', function () {
        this.assert(
            flag(this, 'object')
          , 'expected #{this} to be truthy'
          , 'expected #{this} to be falsy');
      });

      /**
       * ### .true
       *
       * Asserts that the target is `true`.
       *
       *     expect(true).to.be.true;
       *     expect(1).to.not.be.true;
       *
       * @name true
       * @api public
       */

      Assertion.addProperty('true', function () {
        this.assert(
            true === flag(this, 'object')
          , 'expected #{this} to be true'
          , 'expected #{this} to be false'
          , this.negate ? false : true
        );
      });

      /**
       * ### .false
       *
       * Asserts that the target is `false`.
       *
       *     expect(false).to.be.false;
       *     expect(0).to.not.be.false;
       *
       * @name false
       * @api public
       */

      Assertion.addProperty('false', function () {
        this.assert(
            false === flag(this, 'object')
          , 'expected #{this} to be false'
          , 'expected #{this} to be true'
          , this.negate ? true : false
        );
      });

      /**
       * ### .null
       *
       * Asserts that the target is `null`.
       *
       *     expect(null).to.be.null;
       *     expect(undefined).not.to.be.null;
       *
       * @name null
       * @api public
       */

      Assertion.addProperty('null', function () {
        this.assert(
            null === flag(this, 'object')
          , 'expected #{this} to be null'
          , 'expected #{this} not to be null'
        );
      });

      /**
       * ### .undefined
       *
       * Asserts that the target is `undefined`.
       *
       *      expect(undefined).to.be.undefined;
       *      expect(null).to.not.be.undefined;
       *
       * @name undefined
       * @api public
       */

      Assertion.addProperty('undefined', function () {
        this.assert(
            undefined === flag(this, 'object')
          , 'expected #{this} to be undefined'
          , 'expected #{this} not to be undefined'
        );
      });

      /**
       * ### .exist
       *
       * Asserts that the target is neither `null` nor `undefined`.
       *
       *     var foo = 'hi'
       *       , bar = null
       *       , baz;
       *
       *     expect(foo).to.exist;
       *     expect(bar).to.not.exist;
       *     expect(baz).to.not.exist;
       *
       * @name exist
       * @api public
       */

      Assertion.addProperty('exist', function () {
        this.assert(
            null != flag(this, 'object')
          , 'expected #{this} to exist'
          , 'expected #{this} to not exist'
        );
      });


      /**
       * ### .empty
       *
       * Asserts that the target's length is `0`. For arrays, it checks
       * the `length` property. For objects, it gets the count of
       * enumerable keys.
       *
       *     expect([]).to.be.empty;
       *     expect('').to.be.empty;
       *     expect({}).to.be.empty;
       *
       * @name empty
       * @api public
       */

      Assertion.addProperty('empty', function () {
        var obj = flag(this, 'object')
          , expected = obj;

        if (Array.isArray(obj) || 'string' === typeof object) {
          expected = obj.length;
        } else if (typeof obj === 'object') {
          expected = Object.keys(obj).length;
        }

        this.assert(
            !expected
          , 'expected #{this} to be empty'
          , 'expected #{this} not to be empty'
        );
      });

      /**
       * ### .arguments
       *
       * Asserts that the target is an arguments object.
       *
       *     function test () {
       *       expect(arguments).to.be.arguments;
       *     }
       *
       * @name arguments
       * @alias Arguments
       * @api public
       */

      function checkArguments () {
        var obj = flag(this, 'object')
          , type = Object.prototype.toString.call(obj);
        this.assert(
            '[object Arguments]' === type
          , 'expected #{this} to be arguments but got ' + type
          , 'expected #{this} to not be arguments'
        );
      }

      Assertion.addProperty('arguments', checkArguments);
      Assertion.addProperty('Arguments', checkArguments);

      /**
       * ### .equal(value)
       *
       * Asserts that the target is strictly equal (`===`) to `value`.
       * Alternately, if the `deep` flag is set, asserts that
       * the target is deeply equal to `value`.
       *
       *     expect('hello').to.equal('hello');
       *     expect(42).to.equal(42);
       *     expect(1).to.not.equal(true);
       *     expect({ foo: 'bar' }).to.not.equal({ foo: 'bar' });
       *     expect({ foo: 'bar' }).to.deep.equal({ foo: 'bar' });
       *
       * @name equal
       * @alias equals
       * @alias eq
       * @alias deep.equal
       * @param {Mixed} value
       * @api public
       */

      function assertEqual (val) {
        var obj = flag(this, 'object');
        if (flag(this, 'deep')) {
          return this.eql(val);
        } else {
          this.assert(
              val === obj
            , 'expected #{this} to equal #{exp}'
            , 'expected #{this} to not equal #{exp}'
            , val
          );
        }
      }

      Assertion.addMethod('equal', assertEqual);
      Assertion.addMethod('equals', assertEqual);
      Assertion.addMethod('eq', assertEqual);

      /**
       * ### .eql(value)
       *
       * Asserts that the target is deeply equal to `value`.
       *
       *     expect({ foo: 'bar' }).to.eql({ foo: 'bar' });
       *     expect([ 1, 2, 3 ]).to.eql([ 1, 2, 3 ]);
       *
       * @name eql
       * @param {Mixed} value
       * @api public
       */

      Assertion.addMethod('eql', function (obj) {
        this.assert(
            _.eql(obj, flag(this, 'object'))
          , 'expected #{this} to deeply equal #{exp}'
          , 'expected #{this} to not deeply equal #{exp}'
          , obj
        );
      });

      /**
       * ### .above(value)
       *
       * Asserts that the target is greater than `value`.
       *
       *     expect(10).to.be.above(5);
       *
       * Can also be used in conjunction with `length` to
       * assert a minimum length. The benefit being a
       * more informative error message than if the length
       * was supplied directly.
       *
       *     expect('foo').to.have.length.above(2);
       *     expect([ 1, 2, 3 ]).to.have.length.above(2);
       *
       * @name above
       * @alias gt
       * @alias greaterThan
       * @param {Number} value
       * @api public
       */

      function assertAbove (n) {
        var obj = flag(this, 'object');
        if (flag(this, 'doLength')) {
          new Assertion(obj).to.have.property('length');
          var len = obj.length;
          this.assert(
              len > n
            , 'expected #{this} to have a length above #{exp} but got #{act}'
            , 'expected #{this} to not have a length above #{exp}'
            , n
            , len
          );
        } else {
          this.assert(
              obj > n
            , 'expected #{this} to be above ' + n
            , 'expected #{this} to be below ' + n
          );
        }
      }

      Assertion.addMethod('above', assertAbove);
      Assertion.addMethod('gt', assertAbove);
      Assertion.addMethod('greaterThan', assertAbove);

      /**
       * ### .below(value)
       *
       * Asserts that the target is less than `value`.
       *
       *     expect(5).to.be.below(10);
       *
       * Can also be used in conjunction with `length` to
       * assert a maximum length. The benefit being a
       * more informative error message than if the length
       * was supplied directly.
       *
       *     expect('foo').to.have.length.below(4);
       *     expect([ 1, 2, 3 ]).to.have.length.below(4);
       *
       * @name below
       * @alias lt
       * @alias lessThan
       * @param {Number} value
       * @api public
       */

      function assertBelow (n) {
        var obj = flag(this, 'object');
        if (flag(this, 'doLength')) {
          new Assertion(obj).to.have.property('length');
          var len = obj.length;
          this.assert(
              len < n
            , 'expected #{this} to have a length below #{exp} but got #{act}'
            , 'expected #{this} to not have a length below #{exp}'
            , n
            , len
          );
        } else {
          this.assert(
              obj < n
            , 'expected #{this} to be below ' + n
            , 'expected #{this} to be above ' + n
          );
        }
      }

      Assertion.addMethod('below', assertBelow);
      Assertion.addMethod('lt', assertBelow);
      Assertion.addMethod('lessThan', assertBelow);

      /**
       * ### .within(start, finish)
       *
       * Asserts that the target is within a range.
       *
       *     expect(7).to.be.within(5,10);
       *
       * Can also be used in conjunction with `length` to
       * assert a length range. The benefit being a
       * more informative error message than if the length
       * was supplied directly.
       *
       *     expect('foo').to.have.length.within(2,4);
       *     expect([ 1, 2, 3 ]).to.have.length.within(2,4);
       *
       * @name within
       * @param {Number} start lowerbound inclusive
       * @param {Number} finish upperbound inclusive
       * @api public
       */

      Assertion.addMethod('within', function (start, finish) {
        var obj = flag(this, 'object')
          , range = start + '..' + finish;
        if (flag(this, 'doLength')) {
          new Assertion(obj).to.have.property('length');
          var len = obj.length;
          this.assert(
              len >= start && len <= finish
            , 'expected #{this} to have a length within ' + range
            , 'expected #{this} to not have a length within ' + range
          );
        } else {
          this.assert(
              obj >= start && obj <= finish
            , 'expected #{this} to be within ' + range
            , 'expected #{this} to not be within ' + range
          );
        }
      });

      /**
       * ### .instanceof(constructor)
       *
       * Asserts that the target is an instance of `constructor`.
       *
       *     var Tea = function (name) { this.name = name; }
       *       , Chai = new Tea('chai');
       *
       *     expect(Chai).to.be.an.instanceof(Tea);
       *     expect([ 1, 2, 3 ]).to.be.instanceof(Array);
       *
       * @name instanceof
       * @param {Constructor} constructor
       * @alias instanceOf
       * @api public
       */

      function assertInstanceOf (constructor) {
        var name = _.getName(constructor);
        this.assert(
            flag(this, 'object') instanceof constructor
          , 'expected #{this} to be an instance of ' + name
          , 'expected #{this} to not be an instance of ' + name
        );
      };

      Assertion.addMethod('instanceof', assertInstanceOf);
      Assertion.addMethod('instanceOf', assertInstanceOf);

      /**
       * ### .property(name, [value])
       *
       * Asserts that the target has a property `name`, optionally asserting that
       * the value of that property is strictly equal to  `value`.
       * If the `deep` flag is set, you can use dot- and bracket-notation for deep
       * references into objects and arrays.
       *
       *     // simple referencing
       *     var obj = { foo: 'bar' };
       *     expect(obj).to.have.property('foo');
       *     expect(obj).to.have.property('foo', 'bar');
       *
       *     // deep referencing
       *     var deepObj = {
       *         green: { tea: 'matcha' }
       *       , teas: [ 'chai', 'matcha', { tea: 'konacha' } ]
       *     };

       *     expect(deepObj).to.have.deep.property('green.tea', 'matcha');
       *     expect(deepObj).to.have.deep.property('teas[1]', 'matcha');
       *     expect(deepObj).to.have.deep.property('teas[2].tea', 'konacha');
       *
       * You can also use an array as the starting point of a `deep.property`
       * assertion, or traverse nested arrays.
       *
       *     var arr = [
       *         [ 'chai', 'matcha', 'konacha' ]
       *       , [ { tea: 'chai' }
       *         , { tea: 'matcha' }
       *         , { tea: 'konacha' } ]
       *     ];
       *
       *     expect(arr).to.have.deep.property('[0][1]', 'matcha');
       *     expect(arr).to.have.deep.property('[1][2].tea', 'konacha');
       *
       * Furthermore, `property` changes the subject of the assertion
       * to be the value of that property from the original object. This
       * permits for further chainable assertions on that property.
       *
       *     expect(obj).to.have.property('foo')
       *       .that.is.a('string');
       *     expect(deepObj).to.have.property('green')
       *       .that.is.an('object')
       *       .that.deep.equals({ tea: 'matcha' });
       *     expect(deepObj).to.have.property('teas')
       *       .that.is.an('array')
       *       .with.deep.property('[2]')
       *         .that.deep.equals({ tea: 'konacha' });
       *
       * @name property
       * @alias deep.property
       * @param {String} name
       * @param {Mixed} value (optional)
       * @returns value of property for chaining
       * @api public
       */

      Assertion.addMethod('property', function (name, val) {
        var descriptor = flag(this, 'deep') ? 'deep property ' : 'property '
          , negate = flag(this, 'negate')
          , obj = flag(this, 'object')
          , value = flag(this, 'deep')
            ? _.getPathValue(name, obj)
            : obj[name];

        if (negate && undefined !== val) {
          if (undefined === value) {
            throw new Error(_.inspect(obj) + ' has no ' + descriptor + _.inspect(name));
          }
        } else {
          this.assert(
              undefined !== value
            , 'expected #{this} to have a ' + descriptor + _.inspect(name)
            , 'expected #{this} to not have ' + descriptor + _.inspect(name));
        }

        if (undefined !== val) {
          this.assert(
              val === value
            , 'expected #{this} to have a ' + descriptor + _.inspect(name) + ' of #{exp}, but got #{act}'
            , 'expected #{this} to not have a ' + descriptor + _.inspect(name) + ' of #{act}'
            , val
            , value
          );
        }

        flag(this, 'object', value);
      });


      /**
       * ### .ownProperty(name)
       *
       * Asserts that the target has an own property `name`.
       *
       *     expect('test').to.have.ownProperty('length');
       *
       * @name ownProperty
       * @alias haveOwnProperty
       * @param {String} name
       * @api public
       */

      function assertOwnProperty (name) {
        var obj = flag(this, 'object');
        this.assert(
            obj.hasOwnProperty(name)
          , 'expected #{this} to have own property ' + _.inspect(name)
          , 'expected #{this} to not have own property ' + _.inspect(name)
        );
      }

      Assertion.addMethod('ownProperty', assertOwnProperty);
      Assertion.addMethod('haveOwnProperty', assertOwnProperty);

      /**
       * ### .length(value)
       *
       * Asserts that the target's `length` property has
       * the expected value.
       *
       *     expect([ 1, 2, 3]).to.have.length(3);
       *     expect('foobar').to.have.length(6);
       *
       * Can also be used as a chain precursor to a value
       * comparison for the length property.
       *
       *     expect('foo').to.have.length.above(2);
       *     expect([ 1, 2, 3 ]).to.have.length.above(2);
       *     expect('foo').to.have.length.below(4);
       *     expect([ 1, 2, 3 ]).to.have.length.below(4);
       *     expect('foo').to.have.length.within(2,4);
       *     expect([ 1, 2, 3 ]).to.have.length.within(2,4);
       *
       * @name length
       * @alias lengthOf
       * @param {Number} length
       * @api public
       */

      function assertLengthChain () {
        flag(this, 'doLength', true);
      }

      function assertLength (n) {
        var obj = flag(this, 'object');
        new Assertion(obj).to.have.property('length');
        var len = obj.length;

        this.assert(
            len == n
          , 'expected #{this} to have a length of #{exp} but got #{act}'
          , 'expected #{this} to not have a length of #{act}'
          , n
          , len
        );
      }

      Assertion.addChainableMethod('length', assertLength, assertLengthChain);
      Assertion.addMethod('lengthOf', assertLength, assertLengthChain);

      /**
       * ### .match(regexp)
       *
       * Asserts that the target matches a regular expression.
       *
       *     expect('foobar').to.match(/^foo/);
       *
       * @name match
       * @param {RegExp} RegularExpression
       * @api public
       */

      Assertion.addMethod('match', function (re) {
        var obj = flag(this, 'object');
        this.assert(
            re.exec(obj)
          , 'expected #{this} to match ' + re
          , 'expected #{this} not to match ' + re
        );
      });

      /**
       * ### .string(string)
       *
       * Asserts that the string target contains another string.
       *
       *     expect('foobar').to.have.string('bar');
       *
       * @name string
       * @param {String} string
       * @api public
       */

      Assertion.addMethod('string', function (str) {
        var obj = flag(this, 'object');
        new Assertion(obj).is.a('string');

        this.assert(
            ~obj.indexOf(str)
          , 'expected #{this} to contain ' + _.inspect(str)
          , 'expected #{this} to not contain ' + _.inspect(str)
        );
      });


      /**
       * ### .keys(key1, [key2], [...])
       *
       * Asserts that the target has exactly the given keys, or
       * asserts the inclusion of some keys when using the
       * `include` or `contain` modifiers.
       *
       *     expect({ foo: 1, bar: 2 }).to.have.keys(['foo', 'bar']);
       *     expect({ foo: 1, bar: 2, baz: 3 }).to.contain.keys('foo', 'bar');
       *
       * @name keys
       * @alias key
       * @param {String...|Array} keys
       * @api public
       */

      function assertKeys (keys) {
        var obj = flag(this, 'object')
          , str
          , ok = true;

        keys = keys instanceof Array
          ? keys
          : Array.prototype.slice.call(arguments);

        if (!keys.length) throw new Error('keys required');

        var actual = Object.keys(obj)
          , len = keys.length;

        // Inclusion
        ok = keys.every(function(key){
          return ~actual.indexOf(key);
        });

        // Strict
        if (!flag(this, 'negate') && !flag(this, 'contains')) {
          ok = ok && keys.length == actual.length;
        }

        // Key string
        if (len > 1) {
          keys = keys.map(function(key){
            return _.inspect(key);
          });
          var last = keys.pop();
          str = keys.join(', ') + ', and ' + last;
        } else {
          str = _.inspect(keys[0]);
        }

        // Form
        str = (len > 1 ? 'keys ' : 'key ') + str;

        // Have / include
        str = (flag(this, 'contains') ? 'contain ' : 'have ') + str;

        // Assertion
        this.assert(
            ok
          , 'expected #{this} to ' + str
          , 'expected #{this} to not ' + str
        );
      }

      Assertion.addMethod('keys', assertKeys);
      Assertion.addMethod('key', assertKeys);

      /**
       * ### .throw(constructor)
       *
       * Asserts that the function target will throw a specific error, or specific type of error
       * (as determined using `instanceof`), optionally with a RegExp or string inclusion test
       * for the error's message.
       *
       *     var err = new ReferenceError('This is a bad function.');
       *     var fn = function () { throw err; }
       *     expect(fn).to.throw(ReferenceError);
       *     expect(fn).to.throw(Error);
       *     expect(fn).to.throw(/bad function/);
       *     expect(fn).to.not.throw('good function');
       *     expect(fn).to.throw(ReferenceError, /bad function/);
       *     expect(fn).to.throw(err);
       *     expect(fn).to.not.throw(new RangeError('Out of range.'));
       *
       * Please note that when a throw expectation is negated, it will check each
       * parameter independently, starting with error constructor type. The appropriate way
       * to check for the existence of a type of error but for a message that does not match
       * is to use `and`.
       *
       *     expect(fn).to.throw(ReferenceError)
       *        .and.not.throw(/good function/);
       *
       * @name throw
       * @alias throws
       * @alias Throw
       * @param {ErrorConstructor} constructor
       * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types
       * @api public
       */

      function assertThrows (constructor, msg) {
        var obj = flag(this, 'object');
        new Assertion(obj).is.a('function');

        var thrown = false
          , desiredError = null
          , name = null;

        if (arguments.length === 0) {
          msg = null;
          constructor = null;
        } else if (constructor && (constructor instanceof RegExp || 'string' === typeof constructor)) {
          msg = constructor;
          constructor = null;
        } else if (constructor && constructor instanceof Error) {
          desiredError = constructor;
          constructor = null;
          msg = null;
        } else if (typeof constructor === 'function') {
          name = (new constructor()).name;
        } else {
          constructor = null;
        }

        try {
          obj();
        } catch (err) {
          // first, check desired error
          if (desiredError) {
            this.assert(
                err === desiredError
              , 'expected #{this} to throw ' + _.inspect(desiredError) + ' but ' + _.inspect(err) + ' was thrown'
              , 'expected #{this} to not throw ' + _.inspect(desiredError)
            );
            return this;
          }
          // next, check constructor
          if (constructor) {
            this.assert(
                err instanceof constructor
              , 'expected #{this} to throw ' + name + ' but a ' + err.name + ' was thrown'
              , 'expected #{this} to not throw ' + name );
            if (!msg) return this;
          }
          // next, check message
          if (err.message && msg && msg instanceof RegExp) {
            this.assert(
                msg.exec(err.message)
              , 'expected #{this} to throw error matching ' + msg + ' but got ' + _.inspect(err.message)
              , 'expected #{this} to throw error not matching ' + msg
            );
            return this;
          } else if (err.message && msg && 'string' === typeof msg) {
            this.assert(
                ~err.message.indexOf(msg)
              , 'expected #{this} to throw error including #{exp} but got #{act}'
              , 'expected #{this} to throw error not including #{act}'
              , msg
              , err.message
            );
            return this;
          } else {
            thrown = true;
          }
        }

        var expectedThrown = name ? name : desiredError ? _.inspect(desiredError) : 'an error';

        this.assert(
            thrown === true
          , 'expected #{this} to throw ' + expectedThrown
          , 'expected #{this} to not throw ' + expectedThrown
        );
      };

      Assertion.addMethod('throw', assertThrows);
      Assertion.addMethod('throws', assertThrows);
      Assertion.addMethod('Throw', assertThrows);

      /**
       * ### .respondTo(method)
       *
       * Asserts that the object or class target will respond to a method.
       *
       *     Klass.prototype.bar = function(){};
       *     expect(Klass).to.respondTo('bar');
       *     expect(obj).to.respondTo('bar');
       *
       * To check if a constructor will respond to a static function,
       * set the `itself` flag.
       *
       *    Klass.baz = function(){};
       *    expect(Klass).itself.to.respondTo('baz');
       *
       * @name respondTo
       * @param {String} method
       * @api public
       */

      Assertion.addMethod('respondTo', function (method) {
        var obj = flag(this, 'object')
          , itself = flag(this, 'itself')
          , context = ('function' === typeof obj && !itself)
            ? obj.prototype[method]
            : obj[method];

        this.assert(
            'function' === typeof context
          , 'expected #{this} to respond to ' + _.inspect(method)
          , 'expected #{this} to not respond to ' + _.inspect(method)
        );
      });

      /**
       * ### .itself
       *
       * Sets the `itself` flag, later used by the `respondTo` assertion.
       *
       *    function Foo() {}
       *    Foo.bar = function() {}
       *    Foo.prototype.baz = function() {}
       *
       *    expect(Foo).itself.to.respondTo('bar');
       *    expect(Foo).itself.not.to.respondTo('baz');
       *
       * @name itself
       * @api public
       */

      Assertion.addProperty('itself', function () {
        flag(this, 'itself', true);
      });

      /**
       * ### .satisfy(method)
       *
       * Asserts that the target passes a given truth test.
       *
       *     expect(1).to.satisfy(function(num) { return num > 0; });
       *
       * @name satisfy
       * @param {Function} matcher
       * @api public
       */

      Assertion.addMethod('satisfy', function (matcher) {
        var obj = flag(this, 'object');
        this.assert(
            matcher(obj)
          , 'expected #{this} to satisfy ' + _.inspect(matcher)
          , 'expected #{this} to not satisfy' + _.inspect(matcher)
          , this.negate ? false : true
          , matcher(obj)
        );
      });

      /**
       * ### .closeTo(expected, delta)
       *
       * Asserts that the target is equal `expected`, to within a +/- `delta` range.
       *
       *     expect(1.5).to.be.closeTo(1, 0.5);
       *
       * @name closeTo
       * @param {Number} expected
       * @param {Number} delta
       * @api public
       */

      Assertion.addMethod('closeTo', function (expected, delta) {
        var obj = flag(this, 'object');
        this.assert(
            Math.abs(obj - expected) <= delta
          , 'expected #{this} to be close to ' + expected + ' +/- ' + delta
          , 'expected #{this} not to be close to ' + expected + ' +/- ' + delta
        );
      });

    };

  }); // module: chai/core/assertions.js

  require.register("chai/interface/assert.js", function(module, exports, require){
    /*!
     * chai
     * Copyright(c) 2011-2012 Jake Luer <jake@alogicalparadox.com>
     * MIT Licensed
     */


    module.exports = function (chai, util) {

      /*!
       * Chai dependencies.
       */

      var Assertion = chai.Assertion
        , flag = util.flag;

      /*!
       * Module export.
       */

      /**
       * ### assert(expression, message)
       *
       * Write your own test expressions.
       *
       *     assert('foo' !== 'bar', 'foo is not bar');
       *     assert(Array.isArray([]), 'empty arrays are arrays');
       *
       * @param {Mixed} expression to test for truthiness
       * @param {String} message to display on error
       * @name assert
       * @api public
       */

      var assert = chai.assert = function (express, errmsg) {
        var test = new Assertion(null);
        test.assert(
            express
          , errmsg
          , '[ negation message unavailable ]'
        );
      };

      /**
       * ### .fail(actual, expected, [message], [operator])
       *
       * Throw a failure. Node.js `assert` module-compatible.
       *
       * @name fail
       * @param {Mixed} actual
       * @param {Mixed} expected
       * @param {String} message
       * @param {String} operator
       * @api public
       */

      assert.fail = function (actual, expected, message, operator) {
        throw new chai.AssertionError({
            actual: actual
          , expected: expected
          , message: message
          , operator: operator
          , stackStartFunction: assert.fail
        });
      };

      /**
       * ### .ok(object, [message])
       *
       * Asserts that `object` is truthy.
       *
       *     assert.ok('everything', 'everything is ok');
       *     assert.ok(false, 'this will fail');
       *
       * @name ok
       * @param {Mixed} object to test
       * @param {String} message
       * @api public
       */

      assert.ok = function (val, msg) {
        new Assertion(val, msg).is.ok;
      };

      /**
       * ### .equal(actual, expected, [message])
       *
       * Asserts non-strict equality (`==`) of `actual` and `expected`.
       *
       *     assert.equal(3, '3', '== coerces values to strings');
       *
       * @name equal
       * @param {Mixed} actual
       * @param {Mixed} expected
       * @param {String} message
       * @api public
       */

      assert.equal = function (act, exp, msg) {
        var test = new Assertion(act, msg);

        test.assert(
            exp == flag(test, 'object')
          , 'expected #{this} to equal #{exp}'
          , 'expected #{this} to not equal #{act}'
          , exp
          , act
        );
      };

      /**
       * ### .notEqual(actual, expected, [message])
       *
       * Asserts non-strict inequality (`!=`) of `actual` and `expected`.
       *
       *     assert.notEqual(3, 4, 'these numbers are not equal');
       *
       * @name notEqual
       * @param {Mixed} actual
       * @param {Mixed} expected
       * @param {String} message
       * @api public
       */

      assert.notEqual = function (act, exp, msg) {
        var test = new Assertion(act, msg);

        test.assert(
            exp != flag(test, 'object')
          , 'expected #{this} to not equal #{exp}'
          , 'expected #{this} to equal #{act}'
          , exp
          , act
        );
      };

      /**
       * ### .strictEqual(actual, expected, [message])
       *
       * Asserts strict equality (`===`) of `actual` and `expected`.
       *
       *     assert.strictEqual(true, true, 'these booleans are strictly equal');
       *
       * @name strictEqual
       * @param {Mixed} actual
       * @param {Mixed} expected
       * @param {String} message
       * @api public
       */

      assert.strictEqual = function (act, exp, msg) {
        new Assertion(act, msg).to.equal(exp);
      };

      /**
       * ### .notStrictEqual(actual, expected, [message])
       *
       * Asserts strict inequality (`!==`) of `actual` and `expected`.
       *
       *     assert.notStrictEqual(3, '3', 'no coercion for strict equality');
       *
       * @name notStrictEqual
       * @param {Mixed} actual
       * @param {Mixed} expected
       * @param {String} message
       * @api public
       */

      assert.notStrictEqual = function (act, exp, msg) {
        new Assertion(act, msg).to.not.equal(exp);
      };

      /**
       * ### .deepEqual(actual, expected, [message])
       *
       * Asserts that `actual` is deeply equal to `expected`.
       *
       *     assert.deepEqual({ tea: 'green' }, { tea: 'green' });
       *
       * @name deepEqual
       * @param {Mixed} actual
       * @param {Mixed} expected
       * @param {String} message
       * @api public
       */

      assert.deepEqual = function (act, exp, msg) {
        new Assertion(act, msg).to.eql(exp);
      };

      /**
       * ### .notDeepEqual(actual, expected, [message])
       *
       * Assert that `actual` is not deeply equal to `expected`.
       *
       *     assert.notDeepEqual({ tea: 'green' }, { tea: 'jasmine' });
       *
       * @name notDeepEqual
       * @param {Mixed} actual
       * @param {Mixed} expected
       * @param {String} message
       * @api public
       */

      assert.notDeepEqual = function (act, exp, msg) {
        new Assertion(act, msg).to.not.eql(exp);
      };

      /**
       * ### .isTrue(value, [message])
       *
       * Asserts that `value` is true.
       *
       *     var teaServed = true;
       *     assert.isTrue(teaServed, 'the tea has been served');
       *
       * @name isTrue
       * @param {Mixed} value
       * @param {String} message
       * @api public
       */

      assert.isTrue = function (val, msg) {
        new Assertion(val, msg).is['true'];
      };

      /**
       * ### .isFalse(value, [message])
       *
       * Asserts that `value` is false.
       *
       *     var teaServed = false;
       *     assert.isFalse(teaServed, 'no tea yet? hmm...');
       *
       * @name isFalse
       * @param {Mixed} value
       * @param {String} message
       * @api public
       */

      assert.isFalse = function (val, msg) {
        new Assertion(val, msg).is['false'];
      };

      /**
       * ### .isNull(value, [message])
       *
       * Asserts that `value` is null.
       *
       *     assert.isNull(err, 'there was no error');
       *
       * @name isNull
       * @param {Mixed} value
       * @param {String} message
       * @api public
       */

      assert.isNull = function (val, msg) {
        new Assertion(val, msg).to.equal(null);
      };

      /**
       * ### .isNotNull(value, [message])
       *
       * Asserts that `value` is not null.
       *
       *     var tea = 'tasty chai';
       *     assert.isNotNull(tea, 'great, time for tea!');
       *
       * @name isNotNull
       * @param {Mixed} value
       * @param {String} message
       * @api public
       */

      assert.isNotNull = function (val, msg) {
        new Assertion(val, msg).to.not.equal(null);
      };

      /**
       * ### .isUndefined(value, [message])
       *
       * Asserts that `value` is `undefined`.
       *
       *     var tea;
       *     assert.isUndefined(tea, 'no tea defined');
       *
       * @name isUndefined
       * @param {Mixed} value
       * @param {String} message
       * @api public
       */

      assert.isUndefined = function (val, msg) {
        new Assertion(val, msg).to.equal(undefined);
      };

      /**
       * ### .isDefined(value, [message])
       *
       * Asserts that `value` is not `undefined`.
       *
       *     var tea = 'cup of chai';
       *     assert.isDefined(tea, 'tea has been defined');
       *
       * @name isUndefined
       * @param {Mixed} value
       * @param {String} message
       * @api public
       */

      assert.isDefined = function (val, msg) {
        new Assertion(val, msg).to.not.equal(undefined);
      };

      /**
       * ### .isFunction(value, [message])
       *
       * Asserts that `value` is a function.
       *
       *     function serveTea() { return 'cup of tea'; };
       *     assert.isFunction(serveTea, 'great, we can have tea now');
       *
       * @name isFunction
       * @param {Mixed} value
       * @param {String} message
       * @api public
       */

      assert.isFunction = function (val, msg) {
        new Assertion(val, msg).to.be.a('function');
      };

      /**
       * ### .isNotFunction(value, [message])
       *
       * Asserts that `value` is _not_ a function.
       *
       *     var serveTea = [ 'heat', 'pour', 'sip' ];
       *     assert.isNotFunction(serveTea, 'great, we have listed the steps');
       *
       * @name isNotFunction
       * @param {Mixed} value
       * @param {String} message
       * @api public
       */

      assert.isNotFunction = function (val, msg) {
        new Assertion(val, msg).to.not.be.a('function');
      };

      /**
       * ### .isObject(value, [message])
       *
       * Asserts that `value` is an object (as revealed by
       * `Object.prototype.toString`).
       *
       *     var selection = { name: 'Chai', serve: 'with spices' };
       *     assert.isObject(selection, 'tea selection is an object');
       *
       * @name isObject
       * @param {Mixed} value
       * @param {String} message
       * @api public
       */

      assert.isObject = function (val, msg) {
        new Assertion(val, msg).to.be.a('object');
      };

      /**
       * ### .isNotObject(value, [message])
       *
       * Asserts that `value` is _not_ an object.
       *
       *     var selection = 'chai'
       *     assert.isObject(selection, 'tea selection is not an object');
       *     assert.isObject(null, 'null is not an object');
       *
       * @name isNotObject
       * @param {Mixed} value
       * @param {String} message
       * @api public
       */

      assert.isNotObject = function (val, msg) {
        new Assertion(val, msg).to.not.be.a('object');
      };

      /**
       * ### .isArray(value, [message])
       *
       * Asserts that `value` is an array.
       *
       *     var menu = [ 'green', 'chai', 'oolong' ];
       *     assert.isArray(menu, 'what kind of tea do we want?');
       *
       * @name isArray
       * @param {Mixed} value
       * @param {String} message
       * @api public
       */

      assert.isArray = function (val, msg) {
        new Assertion(val, msg).to.be.an('array');
      };

      /**
       * ### .isNotArray(value, [message])
       *
       * Asserts that `value` is _not_ an array.
       *
       *     var menu = 'green|chai|oolong';
       *     assert.isNotArray(menu, 'what kind of tea do we want?');
       *
       * @name isNotArray
       * @param {Mixed} value
       * @param {String} message
       * @api public
       */

      assert.isNotArray = function (val, msg) {
        new Assertion(val, msg).to.not.be.an('array');
      };

      /**
       * ### .isString(value, [message])
       *
       * Asserts that `value` is a string.
       *
       *     var teaOrder = 'chai';
       *     assert.isString(teaOrder, 'order placed');
       *
       * @name isString
       * @param {Mixed} value
       * @param {String} message
       * @api public
       */

      assert.isString = function (val, msg) {
        new Assertion(val, msg).to.be.a('string');
      };

      /**
       * ### .isNotString(value, [message])
       *
       * Asserts that `value` is _not_ a string.
       *
       *     var teaOrder = 4;
       *     assert.isNotString(teaOrder, 'order placed');
       *
       * @name isNotString
       * @param {Mixed} value
       * @param {String} message
       * @api public
       */

      assert.isNotString = function (val, msg) {
        new Assertion(val, msg).to.not.be.a('string');
      };

      /**
       * ### .isNumber(value, [message])
       *
       * Asserts that `value` is a number.
       *
       *     var cups = 2;
       *     assert.isNumber(cups, 'how many cups');
       *
       * @name isNumber
       * @param {Number} value
       * @param {String} message
       * @api public
       */

      assert.isNumber = function (val, msg) {
        new Assertion(val, msg).to.be.a('number');
      };

      /**
       * ### .isNotNumber(value, [message])
       *
       * Asserts that `value` is _not_ a number.
       *
       *     var cups = '2 cups please';
       *     assert.isNotNumber(cups, 'how many cups');
       *
       * @name isNotNumber
       * @param {Mixed} value
       * @param {String} message
       * @api public
       */

      assert.isNotNumber = function (val, msg) {
        new Assertion(val, msg).to.not.be.a('number');
      };

      /**
       * ### .isBoolean(value, [message])
       *
       * Asserts that `value` is a boolean.
       *
       *     var teaReady = true
       *       , teaServed = false;
       *
       *     assert.isBoolean(teaReady, 'is the tea ready');
       *     assert.isBoolean(teaServed, 'has tea been served');
       *
       * @name isBoolean
       * @param {Mixed} value
       * @param {String} message
       * @api public
       */

      assert.isBoolean = function (val, msg) {
        new Assertion(val, msg).to.be.a('boolean');
      };

      /**
       * ### .isNotBoolean(value, [message])
       *
       * Asserts that `value` is _not_ a boolean.
       *
       *     var teaReady = 'yep'
       *       , teaServed = 'nope';
       *
       *     assert.isNotBoolean(teaReady, 'is the tea ready');
       *     assert.isNotBoolean(teaServed, 'has tea been served');
       *
       * @name isNotBoolean
       * @param {Mixed} value
       * @param {String} message
       * @api public
       */

      assert.isNotBoolean = function (val, msg) {
        new Assertion(val, msg).to.not.be.a('boolean');
      };

      /**
       * ### .typeOf(value, name, [message])
       *
       * Asserts that `value`'s type is `name`, as determined by
       * `Object.prototype.toString`.
       *
       *     assert.typeOf({ tea: 'chai' }, 'object', 'we have an object');
       *     assert.typeOf(['chai', 'jasmine'], 'array', 'we have an array');
       *     assert.typeOf('tea', 'string', 'we have a string');
       *     assert.typeOf(/tea/, 'regexp', 'we have a regular expression');
       *     assert.typeOf(null, 'null', 'we have a null');
       *     assert.typeOf(undefined, 'undefined', 'we have an undefined');
       *
       * @name typeOf
       * @param {Mixed} value
       * @param {String} name
       * @param {String} message
       * @api public
       */

      assert.typeOf = function (val, type, msg) {
        new Assertion(val, msg).to.be.a(type);
      };

      /**
       * ### .notTypeOf(value, name, [message])
       *
       * Asserts that `value`'s type is _not_ `name`, as determined by
       * `Object.prototype.toString`.
       *
       *     assert.notTypeOf('tea', 'number', 'strings are not numbers');
       *
       * @name notTypeOf
       * @param {Mixed} value
       * @param {String} typeof name
       * @param {String} message
       * @api public
       */

      assert.notTypeOf = function (val, type, msg) {
        new Assertion(val, msg).to.not.be.a(type);
      };

      /**
       * ### .instanceOf(object, constructor, [message])
       *
       * Asserts that `value` is an instance of `constructor`.
       *
       *     var Tea = function (name) { this.name = name; }
       *       , chai = new Tea('chai');
       *
       *     assert.instanceOf(chai, Tea, 'chai is an instance of tea');
       *
       * @name instanceOf
       * @param {Object} object
       * @param {Constructor} constructor
       * @param {String} message
       * @api public
       */

      assert.instanceOf = function (val, type, msg) {
        new Assertion(val, msg).to.be.instanceOf(type);
      };

      /**
       * ### .notInstanceOf(object, constructor, [message])
       *
       * Asserts `value` is not an instance of `constructor`.
       *
       *     var Tea = function (name) { this.name = name; }
       *       , chai = new String('chai');
       *
       *     assert.notInstanceOf(chai, Tea, 'chai is not an instance of tea');
       *
       * @name notInstanceOf
       * @param {Object} object
       * @param {Constructor} constructor
       * @param {String} message
       * @api public
       */

      assert.notInstanceOf = function (val, type, msg) {
        new Assertion(val, msg).to.not.be.instanceOf(type);
      };

      /**
       * ### .include(haystack, needle, [message])
       *
       * Asserts that `haystack` includes `needle`. Works
       * for strings and arrays.
       *
       *     assert.include('foobar', 'bar', 'foobar contains string "bar"');
       *     assert.include([ 1, 2, 3 ], 3, 'array contains value');
       *
       * @name include
       * @param {Array|String} haystack
       * @param {Mixed} needle
       * @param {String} message
       * @api public
       */

      assert.include = function (exp, inc, msg) {
        var obj = new Assertion(exp, msg);

        if (Array.isArray(exp)) {
          obj.to.include(inc);
        } else if ('string' === typeof exp) {
          obj.to.contain.string(inc);
        }
      };

      /**
       * ### .match(value, regexp, [message])
       *
       * Asserts that `value` matches the regular expression `regexp`.
       *
       *     assert.match('foobar', /^foo/, 'regexp matches');
       *
       * @name match
       * @param {Mixed} value
       * @param {RegExp} regexp
       * @param {String} message
       * @api public
       */

      assert.match = function (exp, re, msg) {
        new Assertion(exp, msg).to.match(re);
      };

      /**
       * ### .notMatch(value, regexp, [message])
       *
       * Asserts that `value` does not match the regular expression `regexp`.
       *
       *     assert.notMatch('foobar', /^foo/, 'regexp does not match');
       *
       * @name notMatch
       * @param {Mixed} value
       * @param {RegExp} regexp
       * @param {String} message
       * @api public
       */

      assert.notMatch = function (exp, re, msg) {
        new Assertion(exp, msg).to.not.match(re);
      };

      /**
       * ### .property(object, property, [message])
       *
       * Asserts that `object` has a property named by `property`.
       *
       *     assert.property({ tea: { green: 'matcha' }}, 'tea');
       *
       * @name property
       * @param {Object} object
       * @param {String} property
       * @param {String} message
       * @api public
       */

      assert.property = function (obj, prop, msg) {
        new Assertion(obj, msg).to.have.property(prop);
      };

      /**
       * ### .notProperty(object, property, [message])
       *
       * Asserts that `object` does _not_ have a property named by `property`.
       *
       *     assert.notProperty({ tea: { green: 'matcha' }}, 'coffee');
       *
       * @name notProperty
       * @param {Object} object
       * @param {String} property
       * @param {String} message
       * @api public
       */

      assert.notProperty = function (obj, prop, msg) {
        new Assertion(obj, msg).to.not.have.property(prop);
      };

      /**
       * ### .deepProperty(object, property, [message])
       *
       * Asserts that `object` has a property named by `property`, which can be a
       * string using dot- and bracket-notation for deep reference.
       *
       *     assert.deepProperty({ tea: { green: 'matcha' }}, 'tea.green');
       *
       * @name deepProperty
       * @param {Object} object
       * @param {String} property
       * @param {String} message
       * @api public
       */

      assert.deepProperty = function (obj, prop, msg) {
        new Assertion(obj, msg).to.have.deep.property(prop);
      };

      /**
       * ### .notDeepProperty(object, property, [message])
       *
       * Asserts that `object` does _not_ have a property named by `property`, which
       * can be a string using dot- and bracket-notation for deep reference.
       *
       *     assert.notDeepProperty({ tea: { green: 'matcha' }}, 'tea.oolong');
       *
       * @name notDeepProperty
       * @param {Object} object
       * @param {String} property
       * @param {String} message
       * @api public
       */

      assert.notDeepProperty = function (obj, prop, msg) {
        new Assertion(obj, msg).to.not.have.deep.property(prop);
      };

      /**
       * ### .propertyVal(object, property, value, [message])
       *
       * Asserts that `object` has a property named by `property` with value given
       * by `value`.
       *
       *     assert.propertyVal({ tea: 'is good' }, 'tea', 'is good');
       *
       * @name propertyVal
       * @param {Object} object
       * @param {String} property
       * @param {Mixed} value
       * @param {String} message
       * @api public
       */

      assert.propertyVal = function (obj, prop, val, msg) {
        new Assertion(obj, msg).to.have.property(prop, val);
      };

      /**
       * ### .propertyNotVal(object, property, value, [message])
       *
       * Asserts that `object` has a property named by `property`, but with a value
       * different from that given by `value`.
       *
       *     assert.propertyNotVal({ tea: 'is good' }, 'tea', 'is bad');
       *
       * @name propertyNotVal
       * @param {Object} object
       * @param {String} property
       * @param {Mixed} value
       * @param {String} message
       * @api public
       */

      assert.propertyNotVal = function (obj, prop, val, msg) {
        new Assertion(obj, msg).to.not.have.property(prop, val);
      };

      /**
       * ### .deepPropertyVal(object, property, value, [message])
       *
       * Asserts that `object` has a property named by `property` with value given
       * by `value`. `property` can use dot- and bracket-notation for deep
       * reference.
       *
       *     assert.deepPropertyVal({ tea: { green: 'matcha' }}, 'tea.green', 'matcha');
       *
       * @name deepPropertyVal
       * @param {Object} object
       * @param {String} property
       * @param {Mixed} value
       * @param {String} message
       * @api public
       */

      assert.deepPropertyVal = function (obj, prop, val, msg) {
        new Assertion(obj, msg).to.have.deep.property(prop, val);
      };

      /**
       * ### .deepPropertyNotVal(object, property, value, [message])
       *
       * Asserts that `object` has a property named by `property`, but with a value
       * different from that given by `value`. `property` can use dot- and
       * bracket-notation for deep reference.
       *
       *     assert.deepPropertyNotVal({ tea: { green: 'matcha' }}, 'tea.green', 'konacha');
       *
       * @name deepPropertyNotVal
       * @param {Object} object
       * @param {String} property
       * @param {Mixed} value
       * @param {String} message
       * @api public
       */

      assert.deepPropertyNotVal = function (obj, prop, val, msg) {
        new Assertion(obj, msg).to.not.have.deep.property(prop, val);
      };

      /**
       * ### .lengthOf(object, length, [message])
       *
       * Asserts that `object` has a `length` property with the expected value.
       *
       *     assert.lengthOf([1,2,3], 3, 'array has length of 3');
       *     assert.lengthOf('foobar', 5, 'string has length of 6');
       *
       * @name lengthOf
       * @param {Mixed} object
       * @param {Number} length
       * @param {String} message
       * @api public
       */

      assert.lengthOf = function (exp, len, msg) {
        new Assertion(exp, msg).to.have.length(len);
      };

      /**
       * ### .throws(function, [constructor/regexp], [message])
       *
       * Asserts that `function` will throw an error that is an instance of
       * `constructor`, or alternately that it will throw an error with message
       * matching `regexp`.
       *
       *     assert.throw(fn, ReferenceError, 'function throws a reference error');
       *
       * @name throws
       * @alias throw
       * @alias Throw
       * @param {Function} function
       * @param {ErrorConstructor} constructor
       * @param {RegExp} regexp
       * @param {String} message
       * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types
       * @api public
       */

      assert.Throw = function (fn, type, msg) {
        if ('string' === typeof type) {
          msg = type;
          type = null;
        }

        new Assertion(fn, msg).to.Throw(type);
      };

      /**
       * ### .doesNotThrow(function, [constructor/regexp], [message])
       *
       * Asserts that `function` will _not_ throw an error that is an instance of
       * `constructor`, or alternately that it will not throw an error with message
       * matching `regexp`.
       *
       *     assert.doesNotThrow(fn, Error, 'function does not throw');
       *
       * @name doesNotThrow
       * @param {Function} function
       * @param {ErrorConstructor} constructor
       * @param {RegExp} regexp
       * @param {String} message
       * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types
       * @api public
       */

      assert.doesNotThrow = function (fn, type, msg) {
        if ('string' === typeof type) {
          msg = type;
          type = null;
        }

        new Assertion(fn, msg).to.not.Throw(type);
      };

      /**
       * ### .operator(val1, operator, val2, [message])
       *
       * Compares two values using `operator`.
       *
       *     assert.operator(1, '<', 2, 'everything is ok');
       *     assert.operator(1, '>', 2, 'this will fail');
       *
       * @name operator
       * @param {Mixed} val1
       * @param {String} operator
       * @param {Mixed} val2
       * @param {String} message
       * @api public
       */

      assert.operator = function (val, operator, val2, msg) {
        if (!~['==', '===', '>', '>=', '<', '<=', '!=', '!=='].indexOf(operator)) {
          throw new Error('Invalid operator "' + operator + '"');
        }
        var test = new Assertion(eval(val + operator + val2), msg);
        test.assert(
            true === flag(test, 'object')
          , 'expected ' + util.inspect(val) + ' to be ' + operator + ' ' + util.inspect(val2)
          , 'expected ' + util.inspect(val) + ' to not be ' + operator + ' ' + util.inspect(val2) );
      };

      /**
       * ### .closeTo(actual, expected, delta, [message])
       *
       * Asserts that the target is equal `expected`, to within a +/- `delta` range.
       *
       *     assert.closeTo(1.5, 1, 0.5, 'numbers are close');
       *
       * @name closeTo
       * @param {Number} actual
       * @param {Number} expected
       * @param {Number} delta
       * @param {String} message
       * @api public
       */

      assert.closeTo = function (act, exp, delta, msg) {
        new Assertion(act, msg).to.be.closeTo(exp, delta);
      };

      /*!
       * Undocumented / untested
       */

      assert.ifError = function (val, msg) {
        new Assertion(val, msg).to.not.be.ok;
      };

      /*!
       * Aliases.
       */

      (function alias(name, as){
        assert[as] = assert[name];
        return alias;
      })
      ('Throw', 'throw')
      ('Throw', 'throws');
    };

  }); // module: chai/interface/assert.js

  require.register("chai/interface/expect.js", function(module, exports, require){
    /*!
     * chai
     * Copyright(c) 2011-2012 Jake Luer <jake@alogicalparadox.com>
     * MIT Licensed
     */

    module.exports = function (chai, util) {
      chai.expect = function (val, message) {
        return new chai.Assertion(val, message);
      };
    };


  }); // module: chai/interface/expect.js

  require.register("chai/interface/should.js", function(module, exports, require){
    /*!
     * chai
     * Copyright(c) 2011-2012 Jake Luer <jake@alogicalparadox.com>
     * MIT Licensed
     */

    module.exports = function (chai, util) {
      var Assertion = chai.Assertion;

      function loadShould () {
        // modify Object.prototype to have `should`
        Object.defineProperty(Object.prototype, 'should',
          { set: function () {}
          , get: function(){
              if (this instanceof String || this instanceof Number) {
                return new Assertion(this.constructor(this));
              } else if (this instanceof Boolean) {
                return new Assertion(this == true);
              }
              return new Assertion(this);
            }
          , configurable: true
        });

        var should = {};

        should.equal = function (val1, val2) {
          new Assertion(val1).to.equal(val2);
        };

        should.Throw = function (fn, errt, errs) {
          new Assertion(fn).to.Throw(errt, errs);
        };

        should.exist = function (val) {
          new Assertion(val).to.exist;
        }

        // negation
        should.not = {}

        should.not.equal = function (val1, val2) {
          new Assertion(val1).to.not.equal(val2);
        };

        should.not.Throw = function (fn, errt, errs) {
          new Assertion(fn).to.not.Throw(errt, errs);
        };

        should.not.exist = function (val) {
          new Assertion(val).to.not.exist;
        }

        should['throw'] = should['Throw'];
        should.not['throw'] = should.not['Throw'];

        return should;
      };

      chai.should = loadShould;
      chai.Should = loadShould;
    };

  }); // module: chai/interface/should.js

  require.register("chai/utils/addChainableMethod.js", function(module, exports, require){
    /*!
     * Chai - addChainingMethod utility
     * Copyright(c) 2012 Jake Luer <jake@alogicalparadox.com>
     * MIT Licensed
     */

    /*!
     * Module dependencies
     */

    var transferFlags = require('./transferFlags');

    /**
     * ### addChainableMethod (ctx, name, method, chainingBehavior)
     *
     * Adds a method to an object, such that the method can also be chained.
     *
     *     utils.addChainableMethod(chai.Assertion.prototype, 'foo', function (str) {
     *       var obj = utils.flag(this, 'object');
     *       new chai.Assertion(obj).to.be.equal(str);
     *     });
     *
     * Can also be accessed directly from `chai.Assertion`.
     *
     *     chai.Assertion.addChainableMethod('foo', fn, chainingBehavior);
     *
     * The result can then be used as both a method assertion, executing both `method` and
     * `chainingBehavior`, or as a language chain, which only executes `chainingBehavior`.
     *
     *     expect(fooStr).to.be.foo('bar');
     *     expect(fooStr).to.be.foo.equal('foo');
     *
     * @param {Object} ctx object to which the method is added
     * @param {String} name of method to add
     * @param {Function} method function to be used for `name`, when called
     * @param {Function} chainingBehavior function to be called every time the property is accessed
     * @name addChainableMethod
     * @api public
     */

    module.exports = function (ctx, name, method, chainingBehavior) {
      if (typeof chainingBehavior !== 'function')
        chainingBehavior = function () { };

      Object.defineProperty(ctx, name,
        { get: function () {
            chainingBehavior.call(this);

            var assert = function () {
              var result = method.apply(this, arguments);
              return result === undefined ? this : result;
            };

            // Re-enumerate every time to better accomodate plugins.
            var asserterNames = Object.getOwnPropertyNames(ctx);
            asserterNames.forEach(function (asserterName) {
              var pd = Object.getOwnPropertyDescriptor(ctx, asserterName)
                , functionProtoPD = Object.getOwnPropertyDescriptor(Function.prototype, asserterName);
              // Avoid trying to overwrite things that we can't, like `length` and `arguments`.
              if (functionProtoPD && !functionProtoPD.configurable) return;
              if (asserterName === 'arguments') return; // @see chaijs/chai/issues/69
              Object.defineProperty(assert, asserterName, pd);
            });

            transferFlags(this, assert);
            return assert;
          }
        , configurable: true
      });
    };

  }); // module: chai/utils/addChainableMethod.js

  require.register("chai/utils/addMethod.js", function(module, exports, require){
    /*!
     * Chai - addMethod utility
     * Copyright(c) 2012 Jake Luer <jake@alogicalparadox.com>
     * MIT Licensed
     */

    /**
     * ### .addMethod (ctx, name, method)
     *
     * Adds a method to the prototype of an object.
     *
     *     utils.addMethod(chai.Assertion.prototype, 'foo', function (str) {
     *       var obj = utils.flag(this, 'object');
     *       new chai.Assertion(obj).to.be.equal(str);
     *     });
     *
     * Can also be accessed directly from `chai.Assertion`.
     *
     *     chai.Assertion.addMethod('foo', fn);
     *
     * Then can be used as any other assertion.
     *
     *     expect(fooStr).to.be.foo('bar');
     *
     * @param {Object} ctx object to which the method is added
     * @param {String} name of method to add
     * @param {Function} method function to be used for name
     * @name addMethod
     * @api public
     */

    module.exports = function (ctx, name, method) {
      ctx[name] = function () {
        var result = method.apply(this, arguments);
        return result === undefined ? this : result;
      };
    };

  }); // module: chai/utils/addMethod.js

  require.register("chai/utils/addProperty.js", function(module, exports, require){
    /*!
     * Chai - addProperty utility
     * Copyright(c) 2012 Jake Luer <jake@alogicalparadox.com>
     * MIT Licensed
     */

    /**
     * ### addProperty (ctx, name, getter)
     *
     * Adds a property to the prototype of an object.
     *
     *     utils.addProperty(chai.Assertion.prototype, 'foo', function () {
     *       var obj = utils.flag(this, 'object');
     *       new chai.Assertion(obj).to.be.instanceof(Foo);
     *     });
     *
     * Can also be accessed directly from `chai.Assertion`.
     *
     *     chai.Assertion.addProperty('foo', fn);
     *
     * Then can be used as any other assertion.
     *
     *     expect(myFoo).to.be.foo;
     *
     * @param {Object} ctx object to which the property is added
     * @param {String} name of property to add
     * @param {Function} getter function to be used for name
     * @name addProperty
     * @api public
     */

    module.exports = function (ctx, name, getter) {
      Object.defineProperty(ctx, name,
        { get: function () {
            var result = getter.call(this);
            return result === undefined ? this : result;
          }
        , configurable: true
      });
    };

  }); // module: chai/utils/addProperty.js

  require.register("chai/utils/eql.js", function(module, exports, require){
    // This is directly from Node.js assert
    // https://github.com/joyent/node/blob/f8c335d0caf47f16d31413f89aa28eda3878e3aa/lib/assert.js


    module.exports = _deepEqual;

    // For browser implementation
    if (!Buffer) {
      var Buffer = {
        isBuffer: function () {
          return false;
        }
      };
    }

    function _deepEqual(actual, expected) {
      // 7.1. All identical values are equivalent, as determined by ===.
      if (actual === expected) {
        return true;

      } else if (Buffer.isBuffer(actual) && Buffer.isBuffer(expected)) {
        if (actual.length != expected.length) return false;

        for (var i = 0; i < actual.length; i++) {
          if (actual[i] !== expected[i]) return false;
        }

        return true;

      // 7.2. If the expected value is a Date object, the actual value is
      // equivalent if it is also a Date object that refers to the same time.
      } else if (actual instanceof Date && expected instanceof Date) {
        return actual.getTime() === expected.getTime();

      // 7.3. Other pairs that do not both pass typeof value == 'object',
      // equivalence is determined by ==.
      } else if (typeof actual != 'object' && typeof expected != 'object') {
        return actual === expected;

      // 7.4. For all other Object pairs, including Array objects, equivalence is
      // determined by having the same number of owned properties (as verified
      // with Object.prototype.hasOwnProperty.call), the same set of keys
      // (although not necessarily the same order), equivalent values for every
      // corresponding key, and an identical 'prototype' property. Note: this
      // accounts for both named and indexed properties on Arrays.
      } else {
        return objEquiv(actual, expected);
      }
    }

    function isUndefinedOrNull(value) {
      return value === null || value === undefined;
    }

    function isArguments(object) {
      return Object.prototype.toString.call(object) == '[object Arguments]';
    }

    function objEquiv(a, b) {
      if (isUndefinedOrNull(a) || isUndefinedOrNull(b))
        return false;
      // an identical 'prototype' property.
      if (a.prototype !== b.prototype) return false;
      //~~~I've managed to break Object.keys through screwy arguments passing.
      //   Converting to array solves the problem.
      if (isArguments(a)) {
        if (!isArguments(b)) {
          return false;
        }
        a = pSlice.call(a);
        b = pSlice.call(b);
        return _deepEqual(a, b);
      }
      try {
        var ka = Object.keys(a),
            kb = Object.keys(b),
            key, i;
      } catch (e) {//happens when one is a string literal and the other isn't
        return false;
      }
      // having the same number of owned properties (keys incorporates
      // hasOwnProperty)
      if (ka.length != kb.length)
        return false;
      //the same set of keys (although not necessarily the same order),
      ka.sort();
      kb.sort();
      //~~~cheap key test
      for (i = ka.length - 1; i >= 0; i--) {
        if (ka[i] != kb[i])
          return false;
      }
      //equivalent values for every corresponding key, and
      //~~~possibly expensive deep test
      for (i = ka.length - 1; i >= 0; i--) {
        key = ka[i];
        if (!_deepEqual(a[key], b[key])) return false;
      }
      return true;
    }
  }); // module: chai/utils/eql.js

  require.register("chai/utils/flag.js", function(module, exports, require){
    /*!
     * Chai - flag utili
Download .txt
gitextract_1j73emsu/

├── .gitignore
├── Gruntfile.js
├── LICENSE-MIT.txt
├── README.md
├── bower.json
├── dist/
│   ├── LargeLocalStorage.js
│   └── contrib/
│       ├── S3Link.js
│       └── URLCache.js
├── examples/
│   └── album/
│       ├── app.js
│       ├── index.html
│       └── main.css
├── package.json
├── src/
│   ├── LargeLocalStorage.js
│   ├── contrib/
│   │   ├── S3Link.js
│   │   └── URLCache.js
│   ├── errors.js
│   ├── footer.js
│   ├── header.js
│   ├── impls/
│   │   ├── FilesystemAPIProvider.js
│   │   ├── IndexedDBProvider.js
│   │   ├── LocalStorageProvider.js
│   │   ├── WebSQLProvider.js
│   │   └── utils.js
│   └── pipeline.js
└── test/
    ├── index.html
    ├── lib/
    │   ├── chai.js
    │   ├── expect.js
    │   ├── mocha/
    │   │   ├── mocha.css
    │   │   └── mocha.js
    │   └── sinon.js
    ├── runner/
    │   └── mocha.js
    └── spec/
        ├── LargeLocalStorageTest.js
        └── URLCacheTest.js
Download .txt
SYMBOL INDEX (270 symbols across 19 files)

FILE: dist/LargeLocalStorage.js
  function definition (line 4) | function definition(Q) {

FILE: dist/contrib/S3Link.js
  function S3Link (line 2) | function S3Link(config) {

FILE: dist/contrib/URLCache.js
  function defaults (line 6) | function defaults(options, defaultOptions) {
  function add (line 15) | function add(docKey, attachKey, url) {
  function addAll (line 30) | function addAll(urlEntries) {
  function expunge (line 36) | function expunge(docKey, attachKey, needsRevoke) {
  function expungeByUrl (line 58) | function expungeByUrl(url) {
  function URLCache (line 66) | function URLCache(llspipe, options) {

FILE: examples/album/app.js
  function bind (line 24) | function bind() {
  function Album (line 28) | function Album($el) {
  function copyDragover (line 109) | function copyDragover(e) {
  function foreach (line 116) | function foreach(cb, arr) {
  function isImage (line 122) | function isImage(file) {
  function keep (line 126) | function keep(pred, arr) {
  function not (line 130) | function not(pred) {
  function filter (line 136) | function filter(pred, arr) {

FILE: src/LargeLocalStorage.js
  function defaults (line 12) | function defaults(options, defaultOptions) {
  function selectImplementation (line 33) | function selectImplementation(config) {
  function copy (line 57) | function copy(obj) {
  function handleDataMigration (line 66) | function handleDataMigration(storageInstance, config, previousProviderTy...
  function LargeLocalStorage (line 171) | function LargeLocalStorage(config) {
  function writeAttachments (line 549) | function writeAttachments(docKey, attachments, storage) {
  function copyDocs (line 558) | function copyDocs(docKeys, oldStorage, newStorage) {

FILE: src/contrib/S3Link.js
  function S3Link (line 2) | function S3Link(config) {

FILE: src/contrib/URLCache.js
  function defaults (line 6) | function defaults(options, defaultOptions) {
  function add (line 15) | function add(docKey, attachKey, url) {
  function addAll (line 30) | function addAll(urlEntries) {
  function expunge (line 36) | function expunge(docKey, attachKey, needsRevoke) {
  function expungeByUrl (line 58) | function expungeByUrl(url) {
  function URLCache (line 66) | function URLCache(llspipe, options) {

FILE: src/impls/FilesystemAPIProvider.js
  function makeErrorHandler (line 4) | function makeErrorHandler(deferred, finalDeferred) {
  function getAttachmentPath (line 19) | function getAttachmentPath(docKey, attachKey) {
  function readDirEntries (line 28) | function readDirEntries(reader, result) {
  function _readDirEntries (line 36) | function _readDirEntries(reader, result, deferred) {
  function entryToFile (line 49) | function entryToFile(entry, cb, eb) {
  function entryToURL (line 53) | function entryToURL(entry) {
  function FSAPI (line 57) | function FSAPI(fs, numBytes, prefix) {

FILE: src/impls/IndexedDBProvider.js
  function IDB (line 9) | function IDB(db) {
  function continuation (line 265) | function continuation(data) {
  function createObjectStore (line 315) | function createObjectStore(db) {

FILE: src/impls/WebSQLProvider.js
  function WSQL (line 7) | function WSQL(db) {

FILE: src/impls/utils.js
  function _mapAsync (line 69) | function _mapAsync(fn, data, result, deferred) {

FILE: src/pipeline.js
  function PipeContext (line 5) | function PipeContext(handlers, nextMehod, end) {
  function indexOfHandler (line 34) | function indexOfHandler(handlers, len, target) {
  function forward (line 45) | function forward(ctx) {
  function coerce (line 49) | function coerce(methodNames, handler) {
  function createPipeline (line 138) | function createPipeline(pipedMethodNames) {

FILE: test/lib/chai.js
  function require (line 13) | function require(p) {
  function Assertion (line 177) | function Assertion (obj, msg, stack) {
  function AssertionError (line 274) | function AssertionError (options) {
  function an (line 399) | function an(type) {
  function includeChainingBehavior (line 433) | function includeChainingBehavior () {
  function include (line 437) | function include (val) {
  function checkArguments (line 624) | function checkArguments () {
  function assertEqual (line 658) | function assertEqual (val) {
  function assertAbove (line 720) | function assertAbove (n) {
  function assertBelow (line 767) | function assertBelow (n) {
  function assertInstanceOf (line 850) | function assertInstanceOf (constructor) {
  function assertOwnProperty (line 966) | function assertOwnProperty (name) {
  function assertLengthChain (line 1003) | function assertLengthChain () {
  function assertLength (line 1007) | function assertLength (n) {
  function assertKeys (line 1085) | function assertKeys (keys) {
  function assertThrows (line 1170) | function assertThrows (constructor, msg) {
  function loadShould (line 2364) | function loadShould () {
  function _deepEqual (line 2595) | function _deepEqual(actual, expected) {
  function isUndefinedOrNull (line 2630) | function isUndefinedOrNull(value) {
  function isArguments (line 2634) | function isArguments(object) {
  function objEquiv (line 2638) | function objEquiv(a, b) {
  function parsePath (line 2879) | function parsePath (path) {
  function _getPathValue (line 2904) | function _getPathValue (parsed, obj) {
  function inspect (line 3049) | function inspect(obj, showHidden, depth, colors) {
  function formatValue (line 3088) | function formatValue(ctx, value, recurseTimes) {
  function formatPrimitive (line 3196) | function formatPrimitive(ctx, value) {
  function formatError (line 3220) | function formatError(value) {
  function formatArray (line 3225) | function formatArray(ctx, value, recurseTimes, visibleKeys, keys) {
  function formatProperty (line 3245) | function formatProperty(ctx, value, recurseTimes, visibleKeys, key, arra...
  function reduceToSingleString (line 3305) | function reduceToSingleString(output, base, braces) {
  function isArray (line 3325) | function isArray(ar) {
  function isRegExp (line 3330) | function isRegExp(re) {
  function isDate (line 3334) | function isDate(d) {
  function isError (line 3338) | function isError(e) {
  function objectToString (line 3342) | function objectToString(o) {

FILE: test/lib/expect.js
  function expect (line 34) | function expect (obj) {
  function Assertion (line 44) | function Assertion (obj, flag, parent) {
  function bind (line 467) | function bind (fn, scope) {
  function every (line 480) | function every (arr, fn, thisObj) {
  function indexOf (line 497) | function indexOf (arr, o, i) {
  function i (line 519) | function i (obj, showHidden, depth) {
  function isArray (line 700) | function isArray (ar) {
  function isRegExp (line 704) | function isRegExp(re) {
  function isDate (line 716) | function isDate(d) {
  function keys (line 721) | function keys (obj) {
  function map (line 737) | function map (arr, mapper, that) {
  function reduce (line 751) | function reduce (arr, fun) {
  function isUndefinedOrNull (line 834) | function isUndefinedOrNull (value) {
  function isArguments (line 838) | function isArguments (object) {
  function objEquiv (line 842) | function objEquiv (a, b) {
  function f (line 897) | function f(n) {
  function date (line 902) | function date(d, key) {
  function quote (line 928) | function quote(string) {
  function str (line 944) | function str(key, holder) {
  function walk (line 1119) | function walk(holder, key) {

FILE: test/lib/mocha/mocha.js
  function require (line 5) | function require(p){
  function clonePath (line 78) | function clonePath(path) {
  function removeEmpty (line 81) | function removeEmpty(array) {
  function escapeHTML (line 90) | function escapeHTML(s) {
  function contextLines (line 254) | function contextLines(lines) {
  function eofNL (line 257) | function eofNL(curRange, i, current) {
  function isArray (line 429) | function isArray(obj) {
  function EventEmitter (line 439) | function EventEmitter(){}
  function on (line 474) | function on () {
  function Progress (line 618) | function Progress() {
  function Context (line 766) | function Context(){}
  function Hook (line 847) | function Hook(title, fn) {
  function F (line 856) | function F(){}
  function visit (line 1054) | function visit(obj) {
  function image (line 1406) | function image(name) {
  function Mocha (line 1428) | function Mocha(options) {
  function parse (line 1725) | function parse(str) {
  function short (line 1764) | function short(ms) {
  function long (line 1780) | function long(ms) {
  function plural (line 1792) | function plural(ms, n, name) {
  function Base (line 2012) | function Base(runner) {
  function pad (line 2120) | function pad(str, len) {
  function inlineDiff (line 2134) | function inlineDiff(err, escape) {
  function unifiedDiff (line 2168) | function unifiedDiff(err, escape) {
  function errorDiff (line 2200) | function errorDiff(err, type, escape) {
  function escapeInvisibles (line 2217) | function escapeInvisibles(line) {
  function colorLines (line 2232) | function colorLines(name, str) {
  function stringify (line 2246) | function stringify(obj) {
  function sameType (line 2260) | function sameType(a, b) {
  function Doc (line 2290) | function Doc(runner) {
  function Dot (line 2350) | function Dot(runner) {
  function F (line 2390) | function F(){}
  function HTMLCov (line 2419) | function HTMLCov(runner) {
  function coverageClass (line 2443) | function coverageClass(n) {
  function HTML (line 2496) | function HTML(runner, root) {
  function error (line 2635) | function error(msg) {
  function fragment (line 2643) | function fragment(html) {
  function hideSuitesWithout (line 2663) | function hideSuitesWithout(classname) {
  function unhide (line 2675) | function unhide() {
  function text (line 2686) | function text(el, str) {
  function on (line 2698) | function on(el, event, fn) {
  function JSONCov (line 2752) | function JSONCov(runner, output) {
  function map (line 2795) | function map(cov) {
  function coverage (line 2834) | function coverage(filename, data) {
  function clean (line 2877) | function clean(test) {
  function List (line 2909) | function List(runner) {
  function clean (line 2942) | function clean(test) {
  function JSONReporter (line 2974) | function JSONReporter(runner) {
  function clean (line 3015) | function clean(test) {
  function Landing (line 3065) | function Landing(runner) {
  function F (line 3121) | function F(){}
  function List (line 3151) | function List(runner) {
  function F (line 3192) | function F(){}
  function Markdown (line 3221) | function Markdown(runner) {
  function Min (line 3315) | function Min(runner) {
  function F (line 3332) | function F(){}
  function NyanCat (line 3361) | function NyanCat(runner) {
  function draw (line 3425) | function draw(color, n) {
  function write (line 3592) | function write(string) {
  function F (line 3600) | function F(){}
  function Progress (line 3638) | function Progress(runner, options) {
  function F (line 3694) | function F(){}
  function Spec (line 3725) | function Spec(runner) {
  function F (line 3789) | function F(){}
  function TAP (line 3820) | function TAP(runner) {
  function title (line 3868) | function title(test) {
  function XUnit (line 3907) | function XUnit(runner) {
  function F (line 3941) | function F(){}
  function test (line 3951) | function test(test) {
  function tag (line 3973) | function tag(name, attrs, close, content) {
  function cdata (line 3991) | function cdata(str) {
  function Runnable (line 4037) | function Runnable(title, fn) {
  function F (line 4051) | function F(){}
  function multiple (line 4173) | function multiple(err) {
  function done (line 4180) | function done(err) {
  function Runner (line 4273) | function Runner(suite) {
  function F (line 4298) | function F(){}
  function next (line 4461) | function next(i) {
  function next (line 4505) | function next(suite) {
  function next (line 4604) | function next(err) {
  function next (line 4671) | function next() {
  function done (line 4677) | function done() {
  function uncaught (line 4729) | function uncaught(err){
  function filterLeaks (line 4764) | function filterLeaks(ok, globals) {
  function Suite (line 4840) | function Suite(title, ctx) {
  function F (line 4860) | function F(){}
  function Test (line 5117) | function Test(title, fn) {
  function F (line 5127) | function F(){}
  function ignored (line 5280) | function ignored(path){
  function highlight (line 5392) | function highlight(js) {
  function timeslice (line 5477) | function timeslice() {

FILE: test/lib/sinon.js
  function cb (line 131) | function cb(err, res) {
  function makeDone (line 139) | function makeDone(num) {
  function cb (line 152) | function cb(err, res) {
  function callNext (line 159) | function callNext() {
  function next (line 166) | function next(err, result) {
  function keys (line 285) | function keys(object) {
  function isCircular (line 299) | function isCircular(object, objects) {
  function ascii (line 313) | function ascii(object, processed, indent) {
  function isDOMNode (line 471) | function isDOMNode(obj) {
  function isElement (line 490) | function isElement(obj) {
  function isFunction (line 494) | function isFunction(obj) {
  function mirrorProperties (line 498) | function mirrorProperties(target, source) {
  function isRestorable (line 506) | function isRestorable (obj) {
  function assertType (line 839) | function assertType(value, type, name) {
  function isMatcher (line 853) | function isMatcher(object) {
  function matchObject (line 857) | function matchObject(expectation, actual) {
  function createPropertyMatcher (line 1009) | function createPropertyMatcher(propertyTest, messagePrefix) {
  function throwYieldError (line 1079) | function throwYieldError(proxy, text, args) {
  function createSpyCall (line 1231) | function createSpyCall(spy, thisValue, args, returnValue, exception, id) {
  function spy (line 1270) | function spy(object, property) {
  function matchingFake (line 1283) | function matchingFake(fakes, args, strict) {
  function incrementCallCount (line 1297) | function incrementCallCount() {
  function createCallProperties (line 1306) | function createCallProperties() {
  function createProxy (line 1314) | function createProxy(func) {
  function delegateToCalls (line 1506) | function delegateToCalls(method, matchAny, actual, notCalled) {
  function stub (line 1658) | function stub(object, property, func) {
  function getChangingValue (line 1688) | function getChangingValue(stub, property) {
  function getCallback (line 1697) | function getCallback(stub, args) {
  function getCallbackError (line 1722) | function getCallbackError(stub, func, args) {
  function callCallback (line 1757) | function callCallback(stub, args) {
  function throwsException (line 1783) | function throwsException(error, message) {
  function mock (line 2030) | function mock(object) {
  function each (line 2041) | function each(collection, callback) {
  function callCountInWords (line 2168) | function callCountInWords(callCount) {
  function expectedCallCountInWords (line 2176) | function expectedCallCountInWords(expectation) {
  function receivedMinCalls (line 2197) | function receivedMinCalls(expectation) {
  function receivedMaxCalls (line 2202) | function receivedMaxCalls(expectation) {
  function getFakes (line 2456) | function getFakes(fakeCollection) {
  function each (line 2464) | function each(fakeCollection, method) {
  function compact (line 2474) | function compact(fakeCollection) {
  function addTimer (line 2607) | function addTimer(args, recurring) {
  function parseTime (line 2633) | function parseTime(str) {
  function createObject (line 2659) | function createObject(object) {
  function ClockDate (line 2805) | function ClockDate(year, month, date, hour, minute, second, ms) {
  function mirrorDateProperties (line 2832) | function mirrorDateProperties(target, source) {
  function restore (line 2863) | function restore() {
  function stubGlobal (line 2879) | function stubGlobal(method, clock) {
  function FakeXMLHttpRequest (line 3059) | function FakeXMLHttpRequest() {
  function verifyState (line 3088) | function verifyState(xhr) {
  function each (line 3100) | function each(collection, callback) {
  function some (line 3106) | function some(collection, callback) {
  function verifyRequestSent (line 3178) | function verifyRequestSent(xhr) {
  function verifyHeadersReceived (line 3184) | function verifyHeadersReceived(xhr) {
  function verifyResponseBodyType (line 3190) | function verifyResponseBodyType(body) {
  function F (line 3538) | function F() {}
  function create (line 3540) | function create(proto) {
  function responseArray (line 3545) | function responseArray(handler) {
  function matchOne (line 3563) | function matchOne(response, reqMethod, reqUrl) {
  function match (line 3572) | function match(response, request) {
  function log (line 3593) | function log(response, request) {
  function Server (line 3749) | function Server() {}
  function exposeValue (line 3836) | function exposeValue(sandbox, config, key, value) {
  function prepareSandboxFromConfig (line 3848) | function prepareSandboxFromConfig(config) {
  function test (line 3963) | function test(callback) {
  function createTest (line 4036) | function createTest(property, setUp, tearDown) {
  function testCase (line 4062) | function testCase(tests, prefix) {
  function verifyIsStub (line 4135) | function verifyIsStub() {
  function failAssertion (line 4155) | function failAssertion(object, msg) {
  function mirrorPropAsAssertion (line 4161) | function mirrorPropAsAssertion(name, method, message) {
  function exposedName (line 4188) | function exposedName(prefix, prop) {

FILE: test/runner/mocha.js
  function sendMessage (line 35) | function sendMessage() {

FILE: test/spec/LargeLocalStorageTest.js
  function getAttachment (line 17) | function getAttachment(a, cb) {
  function continuation (line 143) | function continuation(blob1, blob2) {
  function testDataMigration (line 216) | function testDataMigration(done, availableProviders) {
  function getAvailableImplementations (line 282) | function getAvailableImplementations() {

FILE: test/spec/URLCacheTest.js
  function fail (line 2) | function fail(err) {
  function loadTests (line 21) | function loadTests() {
Condensed preview — 33 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (558K chars).
[
  {
    "path": ".gitignore",
    "chars": 46,
    "preview": "bower_components/\nnode_modules/\ndoc/\ntemp.out\n"
  },
  {
    "path": "Gruntfile.js",
    "chars": 1879,
    "preview": "'use strict';\n\nmodule.exports = function (grunt) {\n\trequire('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks)"
  },
  {
    "path": "LICENSE-MIT.txt",
    "chars": 1062,
    "preview": "Copyright (c) 2013 Matt Crinklaw-Vogt\n\nPermission is hereby granted, free of charge, to any person\nobtaining a copy of t"
  },
  {
    "path": "README.md",
    "chars": 4827,
    "preview": "LargeLocalStorage\n=================\n\n\n**Problem:** You need a large key-value store in the browser.\n\nTo make things wors"
  },
  {
    "path": "bower.json",
    "chars": 290,
    "preview": "{\n\t\"name\": \"lls\",\n\t\"version\": \"0.1.3\",\n\t\"main\": \"dist/LargeLocalStorage.js\",\n\t\"ignore\": [\n\t\t\"examples\",\n\t\t\"src\",\n\t\t\"test"
  },
  {
    "path": "dist/LargeLocalStorage.js",
    "chars": 48215,
    "preview": "(function(glob) {\n\tvar undefined = {}.a;\n\n\tfunction definition(Q) {\n\t\n\n/**\n@author Matt Crinklaw-Vogt\n*/\nfunction PipeCo"
  },
  {
    "path": "dist/contrib/S3Link.js",
    "chars": 170,
    "preview": "LargeLocalStorage.contrib.S3Link = (function() {\n\tfunction S3Link(config) {\n\n\t}\n\n\tS3Link.prototype = {\n\t\tpush: function("
  },
  {
    "path": "dist/contrib/URLCache.js",
    "chars": 3707,
    "preview": "LargeLocalStorage.contrib.URLCache = (function() {\n\tvar defaultOptions = {\n\t\tmanageRevocation: true\n\t};\n\n\tfunction defau"
  },
  {
    "path": "examples/album/app.js",
    "chars": 3038,
    "preview": "(function() {\n\t'use strict';\n\n\tvar storage = new LargeLocalStorage({\n\t\tsize: 20 * 1024 * 1024,\n\t\tname: 'lls-album-exampl"
  },
  {
    "path": "examples/album/index.html",
    "chars": 844,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"../../bower_components/bootstrap/dist/css/bo"
  },
  {
    "path": "examples/album/main.css",
    "chars": 365,
    "preview": ".dndArea {\n\tmargin-top: 20px;\n\tborder: 2px dashed #bbb;\n\tborder-radius: 5px;\n\tmin-height: 240px;\n\tpadding: 20px;\n}\n\n.sto"
  },
  {
    "path": "package.json",
    "chars": 1067,
    "preview": "{\n  \"name\": \"lls\",\n  \"version\": \"0.1.3\",\n  \"description\": \"Storage large files and blob in a cross platform way, in the "
  },
  {
    "path": "src/LargeLocalStorage.js",
    "chars": 17566,
    "preview": "var LargeLocalStorage = (function(Q) {\n\tvar sessionMeta = localStorage.getItem('LargeLocalStorage-meta');\n\tif (sessionMe"
  },
  {
    "path": "src/contrib/S3Link.js",
    "chars": 170,
    "preview": "LargeLocalStorage.contrib.S3Link = (function() {\n\tfunction S3Link(config) {\n\n\t}\n\n\tS3Link.prototype = {\n\t\tpush: function("
  },
  {
    "path": "src/contrib/URLCache.js",
    "chars": 3707,
    "preview": "LargeLocalStorage.contrib.URLCache = (function() {\n\tvar defaultOptions = {\n\t\tmanageRevocation: true\n\t};\n\n\tfunction defau"
  },
  {
    "path": "src/errors.js",
    "chars": 14,
    "preview": "define({\n\t\n});"
  },
  {
    "path": "src/footer.js",
    "chars": 193,
    "preview": "\n\treturn LargeLocalStorage;\n}\n\nif (typeof define === 'function' && define.amd) {\n\tdefine(['Q'], definition);\n} else {\n\tg"
  },
  {
    "path": "src/header.js",
    "chars": 69,
    "preview": "(function(glob) {\n\tvar undefined = {}.a;\n\n\tfunction definition(Q) {\n\t"
  },
  {
    "path": "src/impls/FilesystemAPIProvider.js",
    "chars": 9635,
    "preview": "var requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;\nvar persistentStorage = navigator.pe"
  },
  {
    "path": "src/impls/IndexedDBProvider.js",
    "chars": 8420,
    "preview": "var indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.OIndexedDB || window.msIndex"
  },
  {
    "path": "src/impls/LocalStorageProvider.js",
    "chars": 123,
    "preview": "var LocalStorageProvider = (function(Q) {\n\treturn {\n\t\tinit: function() {\n\t\t\treturn Q({type: 'LocalStorage'});\n\t\t}\n\t}\n})("
  },
  {
    "path": "src/impls/WebSQLProvider.js",
    "chars": 6308,
    "preview": "var openDb = window.openDatabase;\nvar WebSQLProvider = (function(Q) {\n\tvar URL = window.URL || window.webkitURL;\n\tvar co"
  },
  {
    "path": "src/impls/utils.js",
    "chars": 1840,
    "preview": "var utils = (function() {\n\treturn {\n\t\tconvertToBase64: function(blob, cb) {\n\t\t\tvar fr = new FileReader();\n\t\t\tfr.onload ="
  },
  {
    "path": "src/pipeline.js",
    "chars": 4053,
    "preview": "\n/**\n@author Matt Crinklaw-Vogt\n*/\nfunction PipeContext(handlers, nextMehod, end) {\n\tthis._handlers = handlers;\n\tthis._n"
  },
  {
    "path": "test/index.html",
    "chars": 1315,
    "preview": "<!doctype html>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge,chrome=1\">\n"
  },
  {
    "path": "test/lib/chai.js",
    "chars": 103581,
    "preview": "!function (name, context, definition) {\n  if (typeof require === 'function' && typeof exports === 'object' && typeof mod"
  },
  {
    "path": "test/lib/expect.js",
    "chars": 33372,
    "preview": "\n(function (global, module) {\n\n  if ('undefined' == typeof module) {\n    var module = { exports: {} }\n      , exports = "
  },
  {
    "path": "test/lib/mocha/mocha.css",
    "chars": 4000,
    "preview": "@charset \"utf-8\";\n\nbody {\n  margin:0;\n}\n\n#mocha {\n  font: 20px/1.5 \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n  mar"
  },
  {
    "path": "test/lib/mocha/mocha.js",
    "chars": 115923,
    "preview": ";(function(){\n\n// CommonJS require()\n\nfunction require(p){\n    var path = require.resolve(p)\n      , mod = require.modul"
  },
  {
    "path": "test/lib/sinon.js",
    "chars": 132683,
    "preview": "/**\n * Sinon.JS 1.7.3, 2013/06/20\n *\n * @author Christian Johansen (christian@cjohansen.no)\n * @author Contributors: htt"
  },
  {
    "path": "test/runner/mocha.js",
    "chars": 966,
    "preview": "(function() {\n  var runner = mocha.run();\n  if(window.PHANTOMJS) {\n    runner.on('test', function(test) {\n      sendMess"
  },
  {
    "path": "test/spec/LargeLocalStorageTest.js",
    "chars": 9633,
    "preview": "(function(lls) {\n\tQ.longStackSupport = true;\n\tQ.onerror = function(err) {\n\t\tconsole.log(err);\n\t\tthrow err;\n\t};\n\n\tvar sto"
  },
  {
    "path": "test/spec/URLCacheTest.js",
    "chars": 3218,
    "preview": "(function(lls) {\n\tfunction fail(err) {\n\t\tconsole.log(err);\n\t\texpect(true).to.equal(false);\n\t}\n\n\tvar blob = new Blob(['<p"
  }
]

About this extraction

This page contains the full source code of the tantaman/LargeLocalStorage GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 33 files (510.1 KB), approximately 130.4k tokens, and a symbol index with 270 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!