diff --git a/complex_queries.js b/complex_queries.js
new file mode 100644
index 0000000000000000000000000000000000000000..00477f8ea14a0d9b320870a4f93aab987faa5b96
--- /dev/null
+++ b/complex_queries.js
@@ -0,0 +1,901 @@
+(function (scope) {
+  "use strict";
+  Object.defineProperty(scope, "ComplexQueries", {
+    configurable: false,
+    enumerable: false,
+    writable: false,
+    value: {}
+  });
+Object.defineProperty(scope.ComplexQueries, "parse", {
+  configurable: false,
+  enumerable: false,
+  writable: false,
+  value: function (string) {
+
+/*
+	Default template driver for JS/CC generated parsers running as
+	browser-based JavaScript/ECMAScript applications.
+	
+	WARNING: 	This parser template will not run as console and has lesser
+				features for debugging than the console derivates for the
+				various JavaScript platforms.
+	
+	Features:
+	- Parser trace messages
+	- Integrated panic-mode error recovery
+	
+	Written 2007, 2008 by Jan Max Meyer, J.M.K S.F. Software Technologies
+	
+	This is in the public domain.
+*/
+
+var _dbg_withtrace		= false;
+var _dbg_string			= new String();
+
+function __dbg_print( text )
+{
+	_dbg_string += text + "\n";
+}
+
+function __lex( info )
+{
+	var state		= 0;
+	var match		= -1;
+	var match_pos	= 0;
+	var start		= 0;
+	var pos			= info.offset + 1;
+
+	do
+	{
+		pos--;
+		state = 0;
+		match = -2;
+		start = pos;
+
+		if( info.src.length <= start )
+			return 19;
+
+		do
+		{
+
+switch( state )
+{
+	case 0:
+		if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 8 ) || ( info.src.charCodeAt( pos ) >= 10 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || info.src.charCodeAt( pos ) == 59 || ( info.src.charCodeAt( pos ) >= 63 && info.src.charCodeAt( pos ) <= 64 ) || ( info.src.charCodeAt( pos ) >= 66 && info.src.charCodeAt( pos ) <= 77 ) || ( info.src.charCodeAt( pos ) >= 80 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1;
+		else if( info.src.charCodeAt( pos ) == 9 ) state = 2;
+		else if( info.src.charCodeAt( pos ) == 40 ) state = 3;
+		else if( info.src.charCodeAt( pos ) == 41 ) state = 4;
+		else if( info.src.charCodeAt( pos ) == 60 || info.src.charCodeAt( pos ) == 62 ) state = 5;
+		else if( info.src.charCodeAt( pos ) == 34 ) state = 11;
+		else if( info.src.charCodeAt( pos ) == 79 ) state = 12;
+		else if( info.src.charCodeAt( pos ) == 32 ) state = 13;
+		else if( info.src.charCodeAt( pos ) == 61 ) state = 14;
+		else if( info.src.charCodeAt( pos ) == 65 ) state = 18;
+		else if( info.src.charCodeAt( pos ) == 78 ) state = 19;
+		else state = -1;
+		break;
+
+	case 1:
+		if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1;
+		else if( info.src.charCodeAt( pos ) == 58 ) state = 6;
+		else state = -1;
+		match = 10;
+		match_pos = pos;
+		break;
+
+	case 2:
+		if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1;
+		else if( info.src.charCodeAt( pos ) == 58 ) state = 6;
+		else state = -1;
+		match = 1;
+		match_pos = pos;
+		break;
+
+	case 3:
+		state = -1;
+		match = 3;
+		match_pos = pos;
+		break;
+
+	case 4:
+		state = -1;
+		match = 4;
+		match_pos = pos;
+		break;
+
+	case 5:
+		if( info.src.charCodeAt( pos ) == 61 ) state = 14;
+		else state = -1;
+		match = 11;
+		match_pos = pos;
+		break;
+
+	case 6:
+		state = -1;
+		match = 8;
+		match_pos = pos;
+		break;
+
+	case 7:
+		state = -1;
+		match = 9;
+		match_pos = pos;
+		break;
+
+	case 8:
+		if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1;
+		else if( info.src.charCodeAt( pos ) == 58 ) state = 6;
+		else state = -1;
+		match = 6;
+		match_pos = pos;
+		break;
+
+	case 9:
+		if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1;
+		else if( info.src.charCodeAt( pos ) == 58 ) state = 6;
+		else state = -1;
+		match = 5;
+		match_pos = pos;
+		break;
+
+	case 10:
+		if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1;
+		else if( info.src.charCodeAt( pos ) == 58 ) state = 6;
+		else state = -1;
+		match = 7;
+		match_pos = pos;
+		break;
+
+	case 11:
+		if( info.src.charCodeAt( pos ) == 34 ) state = 7;
+		else if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 33 ) || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 91 ) || ( info.src.charCodeAt( pos ) >= 93 && info.src.charCodeAt( pos ) <= 254 ) ) state = 11;
+		else if( info.src.charCodeAt( pos ) == 92 ) state = 15;
+		else state = -1;
+		break;
+
+	case 12:
+		if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 81 ) || ( info.src.charCodeAt( pos ) >= 83 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1;
+		else if( info.src.charCodeAt( pos ) == 58 ) state = 6;
+		else if( info.src.charCodeAt( pos ) == 82 ) state = 8;
+		else state = -1;
+		match = 10;
+		match_pos = pos;
+		break;
+
+	case 13:
+		state = -1;
+		match = 1;
+		match_pos = pos;
+		break;
+
+	case 14:
+		state = -1;
+		match = 11;
+		match_pos = pos;
+		break;
+
+	case 15:
+		if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 254 ) ) state = 11;
+		else state = -1;
+		break;
+
+	case 16:
+		if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 67 ) || ( info.src.charCodeAt( pos ) >= 69 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1;
+		else if( info.src.charCodeAt( pos ) == 58 ) state = 6;
+		else if( info.src.charCodeAt( pos ) == 68 ) state = 9;
+		else state = -1;
+		match = 10;
+		match_pos = pos;
+		break;
+
+	case 17:
+		if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 83 ) || ( info.src.charCodeAt( pos ) >= 85 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1;
+		else if( info.src.charCodeAt( pos ) == 58 ) state = 6;
+		else if( info.src.charCodeAt( pos ) == 84 ) state = 10;
+		else state = -1;
+		match = 10;
+		match_pos = pos;
+		break;
+
+	case 18:
+		if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 77 ) || ( info.src.charCodeAt( pos ) >= 79 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1;
+		else if( info.src.charCodeAt( pos ) == 58 ) state = 6;
+		else if( info.src.charCodeAt( pos ) == 78 ) state = 16;
+		else state = -1;
+		match = 10;
+		match_pos = pos;
+		break;
+
+	case 19:
+		if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 78 ) || ( info.src.charCodeAt( pos ) >= 80 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1;
+		else if( info.src.charCodeAt( pos ) == 58 ) state = 6;
+		else if( info.src.charCodeAt( pos ) == 79 ) state = 17;
+		else state = -1;
+		match = 10;
+		match_pos = pos;
+		break;
+
+}
+
+
+			pos++;
+
+		}
+		while( state > -1 );
+
+	}
+	while( 1 > -1 && match == 1 );
+
+	if( match > -1 )
+	{
+		info.att = info.src.substr( start, match_pos - start );
+		info.offset = match_pos;
+		
+
+	}
+	else
+	{
+		info.att = new String();
+		match = -1;
+	}
+
+	return match;
+}
+
+
+function __parse( src, err_off, err_la )
+{
+	var		sstack			= new Array();
+	var		vstack			= new Array();
+	var 	err_cnt			= 0;
+	var		act;
+	var		go;
+	var		la;
+	var		rval;
+	var 	parseinfo		= new Function( "", "var offset; var src; var att;" );
+	var		info			= new parseinfo();
+	
+/* Pop-Table */
+var pop_tab = new Array(
+	new Array( 0/* begin' */, 1 ),
+	new Array( 13/* begin */, 1 ),
+	new Array( 12/* search_text */, 1 ),
+	new Array( 12/* search_text */, 2 ),
+	new Array( 12/* search_text */, 3 ),
+	new Array( 14/* and_expression */, 1 ),
+	new Array( 14/* and_expression */, 3 ),
+	new Array( 15/* boolean_expression */, 2 ),
+	new Array( 15/* boolean_expression */, 1 ),
+	new Array( 16/* expression */, 3 ),
+	new Array( 16/* expression */, 2 ),
+	new Array( 16/* expression */, 1 ),
+	new Array( 17/* value */, 2 ),
+	new Array( 17/* value */, 1 ),
+	new Array( 18/* string */, 1 ),
+	new Array( 18/* string */, 1 )
+);
+
+/* Action-Table */
+var act_tab = new Array(
+	/* State 0 */ new Array( 7/* "NOT" */,5 , 3/* "LEFT_PARENTHESE" */,7 , 8/* "COLUMN" */,8 , 11/* "OPERATOR" */,10 , 10/* "WORD" */,12 , 9/* "STRING" */,13 ),
+	/* State 1 */ new Array( 19/* "$" */,0 ),
+	/* State 2 */ new Array( 19/* "$" */,-1 ),
+	/* State 3 */ new Array( 6/* "OR" */,14 , 7/* "NOT" */,5 , 3/* "LEFT_PARENTHESE" */,7 , 8/* "COLUMN" */,8 , 11/* "OPERATOR" */,10 , 10/* "WORD" */,12 , 9/* "STRING" */,13 , 19/* "$" */,-2 , 4/* "RIGHT_PARENTHESE" */,-2 ),
+	/* State 4 */ new Array( 5/* "AND" */,16 , 19/* "$" */,-5 , 7/* "NOT" */,-5 , 3/* "LEFT_PARENTHESE" */,-5 , 8/* "COLUMN" */,-5 , 11/* "OPERATOR" */,-5 , 10/* "WORD" */,-5 , 9/* "STRING" */,-5 , 6/* "OR" */,-5 , 4/* "RIGHT_PARENTHESE" */,-5 ),
+	/* State 5 */ new Array( 3/* "LEFT_PARENTHESE" */,7 , 8/* "COLUMN" */,8 , 11/* "OPERATOR" */,10 , 10/* "WORD" */,12 , 9/* "STRING" */,13 ),
+	/* State 6 */ new Array( 19/* "$" */,-8 , 7/* "NOT" */,-8 , 3/* "LEFT_PARENTHESE" */,-8 , 8/* "COLUMN" */,-8 , 11/* "OPERATOR" */,-8 , 10/* "WORD" */,-8 , 9/* "STRING" */,-8 , 6/* "OR" */,-8 , 5/* "AND" */,-8 , 4/* "RIGHT_PARENTHESE" */,-8 ),
+	/* State 7 */ new Array( 7/* "NOT" */,5 , 3/* "LEFT_PARENTHESE" */,7 , 8/* "COLUMN" */,8 , 11/* "OPERATOR" */,10 , 10/* "WORD" */,12 , 9/* "STRING" */,13 ),
+	/* State 8 */ new Array( 3/* "LEFT_PARENTHESE" */,7 , 8/* "COLUMN" */,8 , 11/* "OPERATOR" */,10 , 10/* "WORD" */,12 , 9/* "STRING" */,13 ),
+	/* State 9 */ new Array( 19/* "$" */,-11 , 7/* "NOT" */,-11 , 3/* "LEFT_PARENTHESE" */,-11 , 8/* "COLUMN" */,-11 , 11/* "OPERATOR" */,-11 , 10/* "WORD" */,-11 , 9/* "STRING" */,-11 , 6/* "OR" */,-11 , 5/* "AND" */,-11 , 4/* "RIGHT_PARENTHESE" */,-11 ),
+	/* State 10 */ new Array( 10/* "WORD" */,12 , 9/* "STRING" */,13 ),
+	/* State 11 */ new Array( 19/* "$" */,-13 , 7/* "NOT" */,-13 , 3/* "LEFT_PARENTHESE" */,-13 , 8/* "COLUMN" */,-13 , 11/* "OPERATOR" */,-13 , 10/* "WORD" */,-13 , 9/* "STRING" */,-13 , 6/* "OR" */,-13 , 5/* "AND" */,-13 , 4/* "RIGHT_PARENTHESE" */,-13 ),
+	/* State 12 */ new Array( 19/* "$" */,-14 , 7/* "NOT" */,-14 , 3/* "LEFT_PARENTHESE" */,-14 , 8/* "COLUMN" */,-14 , 11/* "OPERATOR" */,-14 , 10/* "WORD" */,-14 , 9/* "STRING" */,-14 , 6/* "OR" */,-14 , 5/* "AND" */,-14 , 4/* "RIGHT_PARENTHESE" */,-14 ),
+	/* State 13 */ new Array( 19/* "$" */,-15 , 7/* "NOT" */,-15 , 3/* "LEFT_PARENTHESE" */,-15 , 8/* "COLUMN" */,-15 , 11/* "OPERATOR" */,-15 , 10/* "WORD" */,-15 , 9/* "STRING" */,-15 , 6/* "OR" */,-15 , 5/* "AND" */,-15 , 4/* "RIGHT_PARENTHESE" */,-15 ),
+	/* State 14 */ new Array( 7/* "NOT" */,5 , 3/* "LEFT_PARENTHESE" */,7 , 8/* "COLUMN" */,8 , 11/* "OPERATOR" */,10 , 10/* "WORD" */,12 , 9/* "STRING" */,13 ),
+	/* State 15 */ new Array( 19/* "$" */,-3 , 4/* "RIGHT_PARENTHESE" */,-3 ),
+	/* State 16 */ new Array( 7/* "NOT" */,5 , 3/* "LEFT_PARENTHESE" */,7 , 8/* "COLUMN" */,8 , 11/* "OPERATOR" */,10 , 10/* "WORD" */,12 , 9/* "STRING" */,13 ),
+	/* State 17 */ new Array( 19/* "$" */,-7 , 7/* "NOT" */,-7 , 3/* "LEFT_PARENTHESE" */,-7 , 8/* "COLUMN" */,-7 , 11/* "OPERATOR" */,-7 , 10/* "WORD" */,-7 , 9/* "STRING" */,-7 , 6/* "OR" */,-7 , 5/* "AND" */,-7 , 4/* "RIGHT_PARENTHESE" */,-7 ),
+	/* State 18 */ new Array( 4/* "RIGHT_PARENTHESE" */,23 ),
+	/* State 19 */ new Array( 19/* "$" */,-10 , 7/* "NOT" */,-10 , 3/* "LEFT_PARENTHESE" */,-10 , 8/* "COLUMN" */,-10 , 11/* "OPERATOR" */,-10 , 10/* "WORD" */,-10 , 9/* "STRING" */,-10 , 6/* "OR" */,-10 , 5/* "AND" */,-10 , 4/* "RIGHT_PARENTHESE" */,-10 ),
+	/* State 20 */ new Array( 19/* "$" */,-12 , 7/* "NOT" */,-12 , 3/* "LEFT_PARENTHESE" */,-12 , 8/* "COLUMN" */,-12 , 11/* "OPERATOR" */,-12 , 10/* "WORD" */,-12 , 9/* "STRING" */,-12 , 6/* "OR" */,-12 , 5/* "AND" */,-12 , 4/* "RIGHT_PARENTHESE" */,-12 ),
+	/* State 21 */ new Array( 19/* "$" */,-4 , 4/* "RIGHT_PARENTHESE" */,-4 ),
+	/* State 22 */ new Array( 19/* "$" */,-6 , 7/* "NOT" */,-6 , 3/* "LEFT_PARENTHESE" */,-6 , 8/* "COLUMN" */,-6 , 11/* "OPERATOR" */,-6 , 10/* "WORD" */,-6 , 9/* "STRING" */,-6 , 6/* "OR" */,-6 , 4/* "RIGHT_PARENTHESE" */,-6 ),
+	/* State 23 */ new Array( 19/* "$" */,-9 , 7/* "NOT" */,-9 , 3/* "LEFT_PARENTHESE" */,-9 , 8/* "COLUMN" */,-9 , 11/* "OPERATOR" */,-9 , 10/* "WORD" */,-9 , 9/* "STRING" */,-9 , 6/* "OR" */,-9 , 5/* "AND" */,-9 , 4/* "RIGHT_PARENTHESE" */,-9 )
+);
+
+/* Goto-Table */
+var goto_tab = new Array(
+	/* State 0 */ new Array( 13/* begin */,1 , 12/* search_text */,2 , 14/* and_expression */,3 , 15/* boolean_expression */,4 , 16/* expression */,6 , 17/* value */,9 , 18/* string */,11 ),
+	/* State 1 */ new Array(  ),
+	/* State 2 */ new Array(  ),
+	/* State 3 */ new Array( 12/* search_text */,15 , 14/* and_expression */,3 , 15/* boolean_expression */,4 , 16/* expression */,6 , 17/* value */,9 , 18/* string */,11 ),
+	/* State 4 */ new Array(  ),
+	/* State 5 */ new Array( 16/* expression */,17 , 17/* value */,9 , 18/* string */,11 ),
+	/* State 6 */ new Array(  ),
+	/* State 7 */ new Array( 12/* search_text */,18 , 14/* and_expression */,3 , 15/* boolean_expression */,4 , 16/* expression */,6 , 17/* value */,9 , 18/* string */,11 ),
+	/* State 8 */ new Array( 16/* expression */,19 , 17/* value */,9 , 18/* string */,11 ),
+	/* State 9 */ new Array(  ),
+	/* State 10 */ new Array( 18/* string */,20 ),
+	/* State 11 */ new Array(  ),
+	/* State 12 */ new Array(  ),
+	/* State 13 */ new Array(  ),
+	/* State 14 */ new Array( 12/* search_text */,21 , 14/* and_expression */,3 , 15/* boolean_expression */,4 , 16/* expression */,6 , 17/* value */,9 , 18/* string */,11 ),
+	/* State 15 */ new Array(  ),
+	/* State 16 */ new Array( 14/* and_expression */,22 , 15/* boolean_expression */,4 , 16/* expression */,6 , 17/* value */,9 , 18/* string */,11 ),
+	/* State 17 */ new Array(  ),
+	/* State 18 */ new Array(  ),
+	/* State 19 */ new Array(  ),
+	/* State 20 */ new Array(  ),
+	/* State 21 */ new Array(  ),
+	/* State 22 */ new Array(  ),
+	/* State 23 */ new Array(  )
+);
+
+
+
+/* Symbol labels */
+var labels = new Array(
+	"begin'" /* Non-terminal symbol */,
+	"WHITESPACE" /* Terminal symbol */,
+	"WHITESPACE" /* Terminal symbol */,
+	"LEFT_PARENTHESE" /* Terminal symbol */,
+	"RIGHT_PARENTHESE" /* Terminal symbol */,
+	"AND" /* Terminal symbol */,
+	"OR" /* Terminal symbol */,
+	"NOT" /* Terminal symbol */,
+	"COLUMN" /* Terminal symbol */,
+	"STRING" /* Terminal symbol */,
+	"WORD" /* Terminal symbol */,
+	"OPERATOR" /* Terminal symbol */,
+	"search_text" /* Non-terminal symbol */,
+	"begin" /* Non-terminal symbol */,
+	"and_expression" /* Non-terminal symbol */,
+	"boolean_expression" /* Non-terminal symbol */,
+	"expression" /* Non-terminal symbol */,
+	"value" /* Non-terminal symbol */,
+	"string" /* Non-terminal symbol */,
+	"$" /* Terminal symbol */
+);
+
+
+	
+	info.offset = 0;
+	info.src = src;
+	info.att = new String();
+	
+	if( !err_off )
+		err_off	= new Array();
+	if( !err_la )
+	err_la = new Array();
+	
+	sstack.push( 0 );
+	vstack.push( 0 );
+	
+	la = __lex( info );
+
+	while( true )
+	{
+		act = 25;
+		for( var i = 0; i < act_tab[sstack[sstack.length-1]].length; i+=2 )
+		{
+			if( act_tab[sstack[sstack.length-1]][i] == la )
+			{
+				act = act_tab[sstack[sstack.length-1]][i+1];
+				break;
+			}
+		}
+
+		if( _dbg_withtrace && sstack.length > 0 )
+		{
+			__dbg_print( "\nState " + sstack[sstack.length-1] + "\n" +
+							"\tLookahead: " + labels[la] + " (\"" + info.att + "\")\n" +
+							"\tAction: " + act + "\n" + 
+							"\tSource: \"" + info.src.substr( info.offset, 30 ) + ( ( info.offset + 30 < info.src.length ) ?
+									"..." : "" ) + "\"\n" +
+							"\tStack: " + sstack.join() + "\n" +
+							"\tValue stack: " + vstack.join() + "\n" );
+		}
+		
+			
+		//Panic-mode: Try recovery when parse-error occurs!
+		if( act == 25 )
+		{
+			if( _dbg_withtrace )
+				__dbg_print( "Error detected: There is no reduce or shift on the symbol " + labels[la] );
+			
+			err_cnt++;
+			err_off.push( info.offset - info.att.length );			
+			err_la.push( new Array() );
+			for( var i = 0; i < act_tab[sstack[sstack.length-1]].length; i+=2 )
+				err_la[err_la.length-1].push( labels[act_tab[sstack[sstack.length-1]][i]] );
+			
+			//Remember the original stack!
+			var rsstack = new Array();
+			var rvstack = new Array();
+			for( var i = 0; i < sstack.length; i++ )
+			{
+				rsstack[i] = sstack[i];
+				rvstack[i] = vstack[i];
+			}
+			
+			while( act == 25 && la != 19 )
+			{
+				if( _dbg_withtrace )
+					__dbg_print( "\tError recovery\n" +
+									"Current lookahead: " + labels[la] + " (" + info.att + ")\n" +
+									"Action: " + act + "\n\n" );
+				if( la == -1 )
+					info.offset++;
+					
+				while( act == 25 && sstack.length > 0 )
+				{
+					sstack.pop();
+					vstack.pop();
+					
+					if( sstack.length == 0 )
+						break;
+						
+					act = 25;
+					for( var i = 0; i < act_tab[sstack[sstack.length-1]].length; i+=2 )
+					{
+						if( act_tab[sstack[sstack.length-1]][i] == la )
+						{
+							act = act_tab[sstack[sstack.length-1]][i+1];
+							break;
+						}
+					}
+				}
+				
+				if( act != 25 )
+					break;
+				
+				for( var i = 0; i < rsstack.length; i++ )
+				{
+					sstack.push( rsstack[i] );
+					vstack.push( rvstack[i] );
+				}
+				
+				la = __lex( info );
+			}
+			
+			if( act == 25 )
+			{
+				if( _dbg_withtrace )
+					__dbg_print( "\tError recovery failed, terminating parse process..." );
+				break;
+			}
+
+
+			if( _dbg_withtrace )
+				__dbg_print( "\tError recovery succeeded, continuing" );
+		}
+		
+		/*
+		if( act == 25 )
+			break;
+		*/
+		
+		
+		//Shift
+		if( act > 0 )
+		{			
+			if( _dbg_withtrace )
+				__dbg_print( "Shifting symbol: " + labels[la] + " (" + info.att + ")" );
+		
+			sstack.push( act );
+			vstack.push( info.att );
+			
+			la = __lex( info );
+			
+			if( _dbg_withtrace )
+				__dbg_print( "\tNew lookahead symbol: " + labels[la] + " (" + info.att + ")" );
+		}
+		//Reduce
+		else
+		{		
+			act *= -1;
+			
+			if( _dbg_withtrace )
+				__dbg_print( "Reducing by producution: " + act );
+			
+			rval = void(0);
+			
+			if( _dbg_withtrace )
+				__dbg_print( "\tPerforming semantic action..." );
+			
+switch( act )
+{
+	case 0:
+	{
+		rval = vstack[ vstack.length - 1 ];
+	}
+	break;
+	case 1:
+	{
+		 result = vstack[ vstack.length - 1 ]; 
+	}
+	break;
+	case 2:
+	{
+		 rval = vstack[ vstack.length - 1 ]; 
+	}
+	break;
+	case 3:
+	{
+		 rval = mkComplexQuery('OR',[vstack[ vstack.length - 2 ],vstack[ vstack.length - 1 ]]); 
+	}
+	break;
+	case 4:
+	{
+		 rval = mkComplexQuery('OR',[vstack[ vstack.length - 3 ],vstack[ vstack.length - 1 ]]); 
+	}
+	break;
+	case 5:
+	{
+		 rval = vstack[ vstack.length - 1 ] ; 
+	}
+	break;
+	case 6:
+	{
+		 rval = mkComplexQuery('AND',[vstack[ vstack.length - 3 ],vstack[ vstack.length - 1 ]]); 
+	}
+	break;
+	case 7:
+	{
+		 rval = mkNotQuery(vstack[ vstack.length - 1 ]); 
+	}
+	break;
+	case 8:
+	{
+		 rval = vstack[ vstack.length - 1 ]; 
+	}
+	break;
+	case 9:
+	{
+		 rval = vstack[ vstack.length - 2 ]; 
+	}
+	break;
+	case 10:
+	{
+		 simpleQuerySetId(vstack[ vstack.length - 1 ],vstack[ vstack.length - 2 ].split(':').slice(0,-1).join(':')); rval = vstack[ vstack.length - 1 ]; 
+	}
+	break;
+	case 11:
+	{
+		 rval = vstack[ vstack.length - 1 ]; 
+	}
+	break;
+	case 12:
+	{
+		 vstack[ vstack.length - 1 ].operator = vstack[ vstack.length - 2 ] ; rval = vstack[ vstack.length - 1 ]; 
+	}
+	break;
+	case 13:
+	{
+		 rval = vstack[ vstack.length - 1 ]; 
+	}
+	break;
+	case 14:
+	{
+		 rval = mkSimpleQuery('',vstack[ vstack.length - 1 ]); 
+	}
+	break;
+	case 15:
+	{
+		 rval = mkSimpleQuery('',vstack[ vstack.length - 1 ].split('"').slice(1,-1).join('"')); 
+	}
+	break;
+}
+
+
+
+			if( _dbg_withtrace )
+				__dbg_print( "\tPopping " + pop_tab[act][1] + " off the stack..." );
+				
+			for( var i = 0; i < pop_tab[act][1]; i++ )
+			{
+				sstack.pop();
+				vstack.pop();
+			}
+									
+			go = -1;
+			for( var i = 0; i < goto_tab[sstack[sstack.length-1]].length; i+=2 )
+			{
+				if( goto_tab[sstack[sstack.length-1]][i] == pop_tab[act][0] )
+				{
+					go = goto_tab[sstack[sstack.length-1]][i+1];
+					break;
+				}
+			}
+			
+			if( act == 0 )
+				break;
+				
+			if( _dbg_withtrace )
+				__dbg_print( "\tPushing non-terminal " + labels[ pop_tab[act][0] ] );
+				
+			sstack.push( go );
+			vstack.push( rval );			
+		}
+		
+		if( _dbg_withtrace )
+		{		
+			alert( _dbg_string );
+			_dbg_string = new String();
+		}
+	}
+
+	if( _dbg_withtrace )
+	{
+		__dbg_print( "\nParse complete." );
+		alert( _dbg_string );
+	}
+	
+	return err_cnt;
+}
+
+
+
+var arrayExtend = function () {
+    var j,i,newlist=[],listoflists = arguments;
+    for (j=0; j<listoflists.length; ++j) {
+        for (i=0; i<listoflists[j].length; ++i) {
+            newlist.push(listoflists[j][i]);
+        }
+    }
+    return newlist;
+};
+var mkSimpleQuery = function (id,value,operator) {
+    return {type:'simple',operator:'=',id:id,value:value};
+};
+var mkNotQuery = function (query) {
+    if (query.operator === 'NOT') {
+        return query.query_list[0];
+    }
+    return {type:'complex',operator:'NOT',query_list:[query]};
+};
+var mkComplexQuery = function (operator,query_list) {
+    var i,query_list2 = [];
+    for (i=0; i<query_list.length; ++i) {
+        if (query_list[i].operator === operator) {
+            query_list2 = arrayExtend(query_list2,query_list[i].query_list);
+        } else {
+            query_list2.push(query_list[i]);
+        }
+    }
+    return {type:'complex',operator:operator,query_list:query_list2};
+};
+var simpleQuerySetId = function (query, id) {
+    var i;
+    if (query.type === 'complex') {
+        for (i = 0; i < query.query_list.length; ++i) {
+            simpleQuerySetId (query.query_list[i],id);
+        }
+        return true;
+    }
+    if (query.type === 'simple' && !query.id) {
+        query.id = id;
+        return true;
+    }
+    return false;
+};
+var error_offsets = [];
+var error_lookaheads = [];
+var error_count = 0;
+var result;
+if ( ( error_count = __parse( string, error_offsets, error_lookaheads ) ) > 0 ) {
+    var i;
+    for (i = 0; i < error_count; ++i) {
+        throw new Error ( "Parse error near \"" +
+                          string.substr ( error_offsets[i] ) +
+                          "\", expecting \"" +
+                          error_lookaheads[i].join() + "\"" );
+    }
+}
+
+  return result;
+  }
+
+});
+Object.defineProperty(scope.ComplexQueries,"serialize",{
+    configurable:false,enumerable:false,writable:false,value:function(query){
+        var str_list = [], i;
+        if (query.type === 'complex') {
+            str_list.push ( '(' );
+            for (i=0; i<query.query_list.length; ++i) {
+                str_list.push( scope.ComplexQueries.serialize(query.query_list[i]) );
+                str_list.push( query.operator );
+            }
+            str_list.length --;
+            str_list.push ( ')' );
+            return str_list.join(' ');
+        } else if (query.type === 'simple') {
+            return query.id + (query.id?': ':'') + query.operator + ' "' + query.value + '"';
+        }
+        return query;
+    }
+});
+Object.defineProperty(scope.ComplexQueries,"query",{
+    configurable:false,enumerable:false,writable:false,
+    value: function (query, object_list) {
+        var wildcard_character = typeof query.wildcard_character === 'string' ?
+            query.wildcard_character : '%',
+        operator_actions = {
+            '=':   function (value1, value2) {
+                value1 = '' + value1;
+                return value1.match (convertToRegexp (
+                    value2, wildcard_character
+                )) || false && true;
+            },
+            '!=':  function (value1, value2) {
+                value1 = '' + value1;
+                return !(value1.match (convertToRegexp (
+                    value2, wildcard_character
+                )));
+            },
+            '<':   function (value1, value2) { return value1 < value2; },
+            '<=':  function (value1, value2) { return value1 <= value2; },
+            '>':   function (value1, value2) { return value1 > value2; },
+            '>=':  function (value1, value2) { return value1 >= value2; },
+            'AND': function (item, query_list) {
+                var i;
+                for (i=0; i<query_list.length; ++i) {
+                    if (! itemMatchesQuery (item, query_list[i])) {
+                        return false;
+                    }
+                }
+                return true;
+            },
+            'OR':  function (item, query_list) {
+                var i;
+                for (i=0; i<query_list.length; ++i) {
+                    if (itemMatchesQuery (item, query_list[i])) {
+                        return true;
+                    }
+                }
+                return false;
+            },
+            'NOT': function (item, query_list) {
+                return !itemMatchesQuery(item, query_list[0]);
+            }
+        },
+        convertToRegexp = function (string) {
+            return subString('^' + string.replace(
+                new RegExp(
+                    '([\\{\\}\\(\\)\\^\\$\\&\\.\\*\\?\\\/\\+\\|\\[\\]\\-\\\\])'.
+                        replace (wildcard_character?
+                                 '\\'+wildcard_character:undefined,''),
+                    'g'
+                ),
+                '\\$1'
+            ) + '$',(wildcard_character||undefined), '.*');
+        },
+        subString = function (string, substring, newsubstring) {
+            var res = '', i = 0;
+            if (substring === undefined) {
+                return string;
+            }
+            while (1) {
+                var tmp = string.indexOf(substring,i);
+                if (tmp === -1) {
+                    break;
+                }
+                for (; i < tmp; ++i) {
+                    res += string[i];
+                }
+                res += newsubstring;
+                i += substring.length;
+            }
+            for (; i<string.length; ++i) {
+                res += string[i];
+            }
+            return res;
+        },
+        itemMatchesQuery = function (item, query_object) {
+            var i;
+            if (query_object.type === 'complex') {
+                return operator_actions[query_object.operator](
+                    item, query_object.query_list
+                );
+            } else {
+                if (query_object.id) {
+                    if (typeof item[query_object.id] !== 'undefined') {
+                        return operator_actions[query_object.operator](
+                            item[query_object.id], query_object.value
+                        );
+                    } else {
+                        return false;
+                    }
+                } else {
+                    return true;
+                }
+            }
+        },
+        select = function (list, select_list) {
+            var i;
+            if (select_list.length === 0) {
+                return;
+            }
+            for (i=0; i<list.length; ++i) {
+                var list_value = {}, k;
+                for (k=0; k<select_list.length; ++k) {
+                    list_value[select_list[k]] =
+                        list[i][select_list[k]];
+                }
+                list[i] = list_value;
+            }
+        },
+        sortFunction = function (key, asc) {
+            if (asc === 'descending') {
+                return function (a,b) {
+                    return a[key] < b[key] ? 1 : a[key] > b[key] ? -1 : 0;
+                };
+            }
+            return function (a,b) {
+                return a[key] > b[key] ? 1 : a[key] < b[key] ? -1 : 0;
+            };
+        },
+        mergeList = function (list, list_to_merge, index) {
+            var i,j;
+            for (i = index,j = 0; i < list_to_merge.length + index; ++i, ++j) {
+                list[i] = list_to_merge[j];
+            }
+        },
+        sort = function (list, sort_list) {
+            var i, tmp, key, asc, sortAndMerge = function() {
+                sort(tmp,sort_list.slice(1));
+                mergeList(list,tmp,i-tmp.length);
+                tmp = [list[i]];
+            };
+            if (list.length < 2) {
+                return;
+            }
+            if (sort_list.length === 0) {
+                return;
+            }
+            key = sort_list[0][0];
+            asc = sort_list[0][1];
+            list.sort (sortFunction (key,asc));
+            tmp = [list[0]];
+            for (i = 1; i < list.length; ++i) {
+                if (tmp[0][key] === list[i][key]) {
+                    tmp.push(list[i]);
+                } else {
+                    sortAndMerge();
+                }
+            }
+            sortAndMerge();
+        },
+        limit = function (list, limit_list) {
+            var i;
+            if (typeof limit_list[0] !== 'undefined') {
+                if (typeof limit_list[1] !== 'undefined') {
+                    if (list.length > limit_list[1] + limit_list[0]) {
+                        list.length = limit_list[1] + limit_list[0];
+                    }
+                    list.splice(0,limit_list[0]);
+                } else {
+                    list.length = limit_list[0];
+                }
+            }
+        },
+        ////////////////////////////////////////////////////////////
+        result_list = [], result_list_tmp = [], j;
+        object_list = object_list || [];
+        for (j=0; j<object_list.length; ++j) {
+            if ( itemMatchesQuery (
+                object_list[j], scope.ComplexQueries.parse (query.query)
+            )) {
+                result_list.push(object_list[j]);
+            }
+        }
+        if (query.filter) {
+            select(result_list,query.filter.select_list || []);
+            sort(result_list,query.filter.sort_on || []);
+            limit(result_list,query.filter.limit || []);
+        }
+        return result_list;
+    }
+});
+
+}(jIO));
diff --git a/jio.js b/jio.js
new file mode 100644
index 0000000000000000000000000000000000000000..b304bee52b9206d431831aa922d97404ecec366c
--- /dev/null
+++ b/jio.js
@@ -0,0 +1,2509 @@
+(function (scope, hex_md5) {
+  "use strict";
+  var localstorage;
+  if (typeof localStorage !== "undefined") {
+    localstorage = {
+      getItem: function (item) {
+        var value = localStorage.getItem(item);
+        return value === null ? null : JSON.parse(value);
+      },
+      setItem: function (item, value) {
+        return localStorage.setItem(item, JSON.stringify(value));
+      },
+      removeItem: function (item) {
+        delete localStorage[item];
+      },
+      clone: function () {
+        return JSON.parse(JSON.stringify(localStorage));
+      }
+    };
+  } else {
+    (function () {
+      var pseudo_localStorage = {};
+      localstorage = {
+        getItem: function (item) {
+          var value = pseudo_localStorage[item];
+          return value === undefined ?
+              null : JSON.parse(pseudo_localStorage[item]);
+        },
+        setItem: function (item, value) {
+          pseudo_localStorage[item] = JSON.stringify(value);
+        },
+        removeItem: function (item) {
+          delete pseudo_localStorage[item];
+        },
+        clone: function () {
+          return JSON.parse(JSON.stringify(pseudo_localStorage));
+        }
+      };
+    }());
+  }
+/*jslint indent:2, maxlen: 80, sloppy: true */
+var jioException = function (spec, my) {
+  var that = {};
+  spec = spec || {};
+  my = my || {};
+  that.name = 'jioException';
+  that.message = spec.message || 'Unknown Reason.';
+  that.toString = function () {
+    return that.name + ': ' + that.message;
+  };
+  return that;
+};
+
+var invalidCommandState = function (spec, my) {
+  var that = jioException(spec, my), command = spec.command;
+  spec = spec || {};
+  that.name = 'invalidCommandState';
+  that.toString = function () {
+    return that.name + ': ' +
+      command.getLabel() + ', ' + that.message;
+  };
+  return that;
+};
+
+var invalidStorage = function (spec, my) {
+  var that = jioException(spec, my), type = spec.storage.getType();
+  spec = spec || {};
+  that.name = 'invalidStorage';
+  that.toString = function () {
+    return that.name + ': ' +
+      'Type "' + type + '", ' + that.message;
+  };
+  return that;
+};
+
+var invalidStorageType = function (spec, my) {
+  var that = jioException(spec, my), type = spec.type;
+  that.name = 'invalidStorageType';
+  that.toString = function () {
+    return that.name + ': ' +
+      type + ', ' + that.message;
+  };
+  return that;
+};
+
+var jobNotReadyException = function (spec, my) {
+  var that = jioException(spec, my);
+  that.name = 'jobNotReadyException';
+  return that;
+};
+
+var tooMuchTriesJobException = function (spec, my) {
+  var that = jioException(spec, my);
+  that.name = 'tooMuchTriesJobException';
+  return that;
+};
+
+var invalidJobException = function (spec, my) {
+  var that = jioException(spec, my);
+  that.name = 'invalidJobException';
+  return that;
+};
+var jio = function(spec) {
+/*jslint indent: 2, maxlen: 80, sloppy: true */
+/*global command: true, jobManager: true, job: true */
+var storage = function (spec, my) {
+  var that = {}, priv = {};
+  spec = spec || {};
+  my = my || {};
+  // Attributes //
+  priv.type = spec.type || '';
+
+  // Methods //
+  Object.defineProperty(that, "getType", {
+    configurable: false,
+    enumerable: false,
+    writable: false,
+    value: function () {
+      return priv.type;
+    }
+  });
+
+  /**
+   * Execute the command on this storage.
+   * @method execute
+   * @param  {object} command The command
+   */
+  that.execute = function (command) {
+    that.success = command.success;
+    that.error   = command.error;
+    that.retry   = command.retry;
+    that.end     = command.end;
+    if (that.validate(command)) {
+      command.executeOn(that);
+    }
+  };
+
+  /**
+   * Override this function to validate specifications.
+   * @method isValid
+   * @return {boolean} true if ok, else false.
+   */
+  that.isValid = function () {
+    return true;
+  };
+
+  that.validate = function () {
+    var mess = that.validateState();
+    if (mess) {
+      that.error({
+        "status": 0,
+        "statusText": "Invalid Storage",
+        "error": "invalid_storage",
+        "message": mess,
+        "reason": mess
+      });
+      return false;
+    }
+    return true;
+  };
+
+  /**
+   * Returns a serialized version of this storage.
+   * @method serialized
+   * @return {object} The serialized storage.
+   */
+  that.serialized = function () {
+    var o = that.specToStore() || {};
+    o.type = that.getType();
+    return o;
+  };
+
+  /**
+   * Returns an object containing spec to store on localStorage, in order to
+   * be restored later if something wrong happen.
+   * Override this method!
+   * @method specToStore
+   * @return {object} The spec to store
+   */
+  that.specToStore = function () {
+    return {};
+  };
+
+  /**
+   * Validate the storage state. It returns a empty string all is ok.
+   * @method validateState
+   * @return {string} empty: ok, else error message.
+   */
+  that.validateState = function () {
+    return '';
+  };
+
+  that.post = function () {
+    setTimeout(function () {
+      that.error({
+        "status": 0,
+        "statusText": "Not Implemented",
+        "error": "not_implemented",
+        "message": "\"Post\" command is not implemented",
+        "reason": "Command not implemented"
+      });
+    });
+  };
+
+  that.put = function () {
+    setTimeout(function () {
+      that.error({
+        "status": 0,
+        "statusText": "Not Implemented",
+        "error": "not_implemented",
+        "message": "\"Put\" command is not implemented",
+        "reason": "Command not implemented"
+      });
+    });
+  };
+
+  that.putAttachment = function () {
+    setTimeout(function () {
+      that.error({
+        "status": 0,
+        "statusText": "Not Implemented",
+        "error": "not_implemented",
+        "message": "\"PutAttachment\" command is not implemented",
+        "reason": "Command not implemented"
+      });
+    });
+  };
+
+  that.get = function () {
+    setTimeout(function () {
+      that.error({
+        "status": 0,
+        "statusText": "Not Implemented",
+        "error": "not_implemented",
+        "message": "\"Get\" command is not implemented",
+        "reason": "Command not implemented"
+      });
+    });
+  };
+
+  that.allDocs = function () {
+    setTimeout(function () {
+      that.error({
+        "status": 0,
+        "statusText": "Not Implemented",
+        "error": "not_implemented",
+        "message": "\"AllDocs\" command is not implemented",
+        "reason": "Command not implemented"
+      });
+    });
+  };
+
+  that.remove = function () {
+    setTimeout(function () {
+      that.error({
+        "status": 0,
+        "statusText": "Not Implemented",
+        "error": "not_implemented",
+        "message": "\"Remove\" command is not implemented",
+        "reason": "Command not implemented"
+      });
+    });
+  };
+
+  that.success = function () {};
+  that.retry   = function () {};
+  that.error   = function () {};
+  that.end     = function () {};  // terminate the current job.
+
+  priv.newCommand = function (method, spec) {
+    var o = spec || {};
+    o.label = method;
+    return command(o, my);
+  };
+
+  priv.storage = my.storage;
+  delete my.storage;
+
+  that.addJob = function (method, storage_spec, doc, option, success, error) {
+    var command_opt = {
+      options: option,
+      callbacks: {success: success, error: error}
+    };
+    if (doc) {
+      if (method === 'get') {
+        command_opt.docid = doc;
+      } else {
+        command_opt.doc = doc;
+      }
+    }
+    jobManager.addJob(job({
+      storage: priv.storage(storage_spec || {}),
+      command: priv.newCommand(method, command_opt)
+    }, my));
+  };
+
+  return that;
+};
+/*jslint indent: 2, maxlen: 80, sloppy: true */
+/*global command: true */
+var allDocsCommand = function (spec, my) {
+  var that = command(spec, my);
+
+  spec = spec || {};
+  my = my || {};
+  // Attributes //
+  // Methods //
+  that.getLabel = function () {
+    return 'allDocs';
+  };
+
+  that.executeOn = function (storage) {
+    storage.allDocs(that);
+  };
+
+  that.canBeRestored = function () {
+    return false;
+  };
+
+  that.validateState = function () {
+    return true;
+  };
+
+  return that;
+};
+/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
+/*global postCommand: true, putCommand: true, getCommand: true,
+         removeCommand: true, allDocsCommand: true,
+         putAttachmentCommand: true, failStatus: true, doneStatus: true,
+         hex_md5: true */
+var command = function (spec, my) {
+  var that = {},
+    priv = {};
+
+  spec = spec || {};
+  my = my || {};
+
+  priv.commandlist = {
+    'post': postCommand,
+    'put': putCommand,
+    'get': getCommand,
+    'remove': removeCommand,
+    'allDocs': allDocsCommand,
+    'putAttachment': putAttachmentCommand
+  };
+  // creates the good command thanks to his label
+  if (spec.label && priv.commandlist[spec.label]) {
+    priv.label = spec.label;
+    delete spec.label;
+    return priv.commandlist[priv.label](spec, my);
+  }
+
+  priv.tried = 0;
+  priv.doc = spec.doc || {};
+  if (typeof priv.doc !== "object") {
+    priv.doc = {
+      "_id": priv.doc.toString()
+    };
+  }
+  priv.docid = spec.docid || priv.doc._id;
+  priv.option = spec.options || {};
+  priv.callbacks = spec.callbacks || {};
+  priv.success = priv.callbacks.success || function () {};
+  priv.error = priv.callbacks.error || function () {};
+  priv.retry = function () {
+    that.error({
+      status: 13,
+      statusText: 'Fail Retry',
+      error: 'fail_retry',
+      message: 'Impossible to retry.',
+      reason: 'Impossible to retry.'
+    });
+  };
+  priv.end = function () {};
+  priv.on_going = false;
+
+  // Methods //
+  /**
+   * Returns a serialized version of this command.
+   * @method serialized
+   * @return {object} The serialized command.
+   */
+  that.serialized = function () {
+    var o = {};
+    o.label = that.getLabel();
+    o.tried = priv.tried;
+    o.doc = that.cloneDoc();
+    o.option = that.cloneOption();
+    return o;
+  };
+
+  /**
+   * Returns the label of the command.
+   * @method getLabel
+   * @return {string} The label.
+   */
+  that.getLabel = function () {
+    return 'command';
+  };
+
+  /**
+   * Gets the document id
+   * @method getDocId
+   * @return {string} The document id
+   */
+  that.getDocId = function () {
+    if (typeof priv.docid !== "string") {
+      return undefined;
+    }
+    return priv.docid.split('/')[0];
+  };
+
+  /**
+   * Gets the attachment id
+   * @method getAttachmentId
+   * @return {string} The attachment id
+   */
+  that.getAttachmentId = function () {
+    if (typeof priv.docid !== "string") {
+      return undefined;
+    }
+    return priv.docid.split('/')[1];
+  };
+
+  /**
+   * Returns the label of the command.
+   * @method getDoc
+   * @return {object} The document.
+   */
+  that.getDoc = function () {
+    return priv.doc;
+  };
+
+  /**
+   * Returns the data of the attachment
+   * @method getAttachmentData
+   * @return {string} The data
+   */
+  that.getAttachmentData = function () {
+    return priv.doc._data || "";
+  };
+
+  /**
+   * Returns the data length of the attachment
+   * @method getAttachmentLength
+   * @return {number} The length
+   */
+  that.getAttachmentLength = function () {
+    return (priv.doc._data || "").length;
+  };
+
+  /**
+   * Returns the mimetype of the attachment
+   * @method getAttachmentMimeType
+   * @return {string} The mimetype
+   */
+  that.getAttachmentMimeType = function () {
+    return priv.doc._mimetype;
+  };
+
+  /**
+   * Generate the md5sum of the attachment data
+   * @method md5SumAttachmentData
+   * @return {string} The md5sum
+   */
+  that.md5SumAttachmentData = function () {
+    return hex_md5(priv.doc._data || "");
+  };
+
+  /**
+   * Returns an information about the document.
+   * @method getDocInfo
+   * @param  {string} infoname The info name.
+   * @return The info value.
+   */
+  that.getDocInfo = function (infoname) {
+    return priv.doc[infoname];
+  };
+
+  /**
+   * Returns the value of an option.
+   * @method getOption
+   * @param  {string} optionname The option name.
+   * @return The option value.
+   */
+  that.getOption = function (optionname) {
+    return priv.option[optionname];
+  };
+
+  /**
+   * Validates the storage.
+   * @param  {object} storage The storage.
+   */
+  that.validate = function (storage) {
+    if (typeof priv.docid === "string" &&
+        !priv.docid.match("^[^\/]+([\/][^\/]+)?$")) {
+      that.error({
+        status: 21,
+        statusText: 'Invalid Document Id',
+        error: 'invalid_document_id',
+        message: 'The document id must be like "abc" or "abc/def".',
+        reason: 'The document id is no like "abc" or "abc/def"'
+      });
+      return false;
+    }
+    if (!that.validateState()) {
+      return false;
+    }
+    return storage.validate();
+  };
+
+  /*
+   * Extend this function
+   */
+  that.validateState = function () {
+    return true;
+  };
+
+    /**
+   * Check if the command can be retried.
+   * @method canBeRetried
+   * @return {boolean} The result
+   */
+  that.canBeRetried = function () {
+    return (priv.option.max_retry === undefined ||
+      priv.option.max_retry === 0 ||
+        priv.tried < priv.option.max_retry);
+  };
+
+  /**
+   * Gets the number time the command has been tried.
+   * @method getTried
+   * @return {number} The number of time the command has been tried
+   */
+  that.getTried = function () {
+    return priv.tried;
+  };
+
+  /**
+   * Delegate actual excecution the storage.
+   * @param {object} storage The storage.
+   */
+  that.execute = function (storage) {
+    if (!priv.on_going) {
+      if (that.validate(storage)) {
+        priv.tried += 1;
+        priv.on_going = true;
+        storage.execute(that);
+      }
+    }
+  };
+  /**
+   * Execute the good method from the storage.
+   * Override this function.
+   * @method executeOn
+   * @param  {object} storage The storage.
+   */
+  that.executeOn = function (storage) {};
+  that.success = function (return_value) {
+    priv.on_going = false;
+    priv.success(return_value);
+    priv.end(doneStatus());
+  };
+  that.retry = function (return_error) {
+    priv.on_going = false;
+    if (that.canBeRetried()) {
+      priv.retry();
+    } else {
+      that.error(return_error);
+    }
+  };
+  that.error = function (return_error) {
+    priv.on_going = false;
+    priv.error(return_error);
+    priv.end(failStatus());
+  };
+  that.end = function () {
+    priv.end(doneStatus());
+  };
+  that.onSuccessDo = function (fun) {
+    if (fun) {
+      priv.success = fun;
+    } else {
+      return priv.success;
+    }
+  };
+  that.onErrorDo = function (fun) {
+    if (fun) {
+      priv.error = fun;
+    } else {
+      return priv.error;
+    }
+  };
+  that.onEndDo = function (fun) {
+    priv.end = fun;
+  };
+  that.onRetryDo = function (fun) {
+    priv.retry = fun;
+  };
+  /**
+   * Is the command can be restored by another JIO : yes.
+   * @method canBeRestored
+   * @return {boolean} true
+   */
+  that.canBeRestored = function () {
+    return true;
+  };
+  /**
+   * Clones the command and returns it.
+   * @method clone
+   * @return {object} The cloned command.
+   */
+  that.clone = function () {
+    return command(that.serialized(), my);
+  };
+  /**
+   * Clones the command options and returns the clone version.
+   * @method cloneOption
+   * @return {object} The clone of the command options.
+   */
+  that.cloneOption = function () {
+    return JSON.parse(JSON.stringify(priv.option));
+  };
+  /**
+   * Clones the document and returns the clone version.
+   * @method cloneDoc
+   * @return {object} The clone of the document.
+   */
+  that.cloneDoc = function () {
+    return JSON.parse(JSON.stringify(priv.doc));
+  };
+  return that;
+};
+/*jslint indent: 2, maxlen: 80, sloppy: true */
+/*global command: true */
+var getCommand = function (spec, my) {
+  var that = command(spec, my);
+
+  spec = spec || {};
+  my = my || {};
+  // Attributes //
+  // Methods //
+  that.getLabel = function () {
+    return 'get';
+  };
+
+  that.validateState = function () {
+    if (!(typeof that.getDocId() === "string" &&
+      that.getDocId() !== "")) {
+      that.error({
+        "status": 20,
+        "statusText": "Document Id Required",
+        "error": "document_id_required",
+        "message": "The document id is not provided",
+        "reason": "Document id is undefined"
+      });
+      return false;
+    }
+    if (typeof that.getAttachmentId() === "string") {
+      if (that.getAttachmentId() === "") {
+        that.error({
+          "status": 23,
+          "statusText": "Invalid Attachment Id",
+          "error": "invalid_attachment_id",
+          "message": "The attachment id must not be an empty string",
+          "reason": "Attachment id is empty"
+        });
+        return false;
+      }
+    }
+    return true;
+  };
+
+  that.executeOn = function (storage) {
+    storage.get(that);
+  };
+
+  that.canBeRestored = function () {
+    return false;
+  };
+
+  return that;
+};
+/*jslint indent: 2, maxlen: 80, sloppy: true */
+/*global command: true */
+var postCommand = function (spec, my) {
+  var that = command(spec, my);
+
+  spec = spec || {};
+  my = my || {};
+
+  // Methods //
+  that.getLabel = function () {
+    return 'post';
+  };
+
+  that.validateState = function () {
+    if (that.getAttachmentId() !== undefined) {
+      that.error({
+        "status": 21,
+        "statusText": "Invalid Document Id",
+        "error": "invalid_document_id",
+        "message": "The document id contains '/' characters " +
+          "which are forbidden",
+        "reason": "Document id contains '/' character(s)"
+      });
+      return false;
+    }
+    return true;
+  };
+  that.executeOn = function (storage) {
+    storage.post(that);
+  };
+  return that;
+};
+/*jslint indent: 2, maxlen: 80, sloppy: true */
+/*global command: true */
+var putAttachmentCommand = function (spec, my) {
+  var that = command(spec, my);
+  spec = spec || {};
+  my = my || {};
+  // Attributes //
+  // Methods //
+  that.getLabel = function () {
+    return 'putAttachment';
+  };
+
+  that.executeOn = function (storage) {
+    storage.putAttachment(that);
+  };
+
+  that.validateState = function () {
+    if (!(typeof that.getDocId() === "string" && that.getDocId() !==
+          "")) {
+      that.error({
+        "status": 20,
+        "statusText": "Document Id Required",
+        "error": "document_id_required",
+        "message": "The document id is not provided",
+        "reason": "Document id is undefined"
+      });
+      return false;
+    }
+    if (typeof that.getAttachmentId() !== "string") {
+      that.error({
+        "status": 22,
+        "statusText": "Attachment Id Required",
+        "error": "attachment_id_required",
+        "message": "The attachment id must be set",
+        "reason": "Attachment id not set"
+      });
+      return false;
+    }
+    if (that.getAttachmentId() === "") {
+      that.error({
+        "status": 23,
+        "statusText": "Invalid Attachment Id",
+        "error": "invalid_attachment_id",
+        "message": "The attachment id must not be an empty string",
+        "reason": "Attachment id is empty"
+      });
+    }
+    return true;
+  };
+
+  return that;
+};
+/*jslint indent: 2, maxlen: 80, sloppy: true */
+/*global command: true */
+var putCommand = function (spec, my) {
+  var that = command(spec, my);
+  spec = spec || {};
+  my = my || {};
+
+  // Methods //
+  that.getLabel = function () {
+    return 'put';
+  };
+
+  that.validateState = function () {
+    if (!(typeof that.getDocId() === "string" && that.getDocId() !==
+        "")) {
+      that.error({
+        "status": 20,
+        "statusText": "Document Id Required",
+        "error": "document_id_required",
+        "message": "The document id is not provided",
+        "reason": "Document id is undefined"
+      });
+      return false;
+    }
+    if (that.getAttachmentId() !== undefined) {
+      that.error({
+        "status": 21,
+        "statusText": "Invalid Document Id",
+        "error": "invalid_document_id",
+        "message": "The document id contains '/' characters " +
+          "which are forbidden",
+        "reason": "Document id contains '/' character(s)"
+      });
+      return false;
+    }
+    return true;
+  };
+  that.executeOn = function (storage) {
+    storage.put(that);
+  };
+  return that;
+};
+/*jslint indent: 2, maxlen: 80, sloppy: true */
+/*global command: true */
+var removeCommand = function (spec, my) {
+  var that = command(spec, my);
+  spec = spec || {};
+  my = my || {};
+  // Attributes //
+  // Methods //
+  that.getLabel = function () {
+    return 'remove';
+  };
+
+  that.validateState = function () {
+    if (!(typeof that.getDocId() === "string" && that.getDocId() !==
+        "")) {
+      that.error({
+        "status": 20,
+        "statusText": "Document Id Required",
+        "error": "document_id_required",
+        "message": "The document id is not provided",
+        "reason": "Document id is undefined"
+      });
+      return false;
+    }
+    if (typeof that.getAttachmentId() === "string") {
+      if (that.getAttachmentId() === "") {
+        that.error({
+          "status": 23,
+          "statusText": "Invalid Attachment Id",
+          "error": "invalid_attachment_id",
+          "message": "The attachment id must not be an empty string",
+          "reason": "Attachment id is empty"
+        });
+        return false;
+      }
+    }
+    return true;
+  };
+
+  that.executeOn = function (storage) {
+    storage.remove(that);
+  };
+
+  return that;
+};
+/*jslint indent: 2, maxlen: 80, sloppy: true */
+/*global jobStatus: true */
+var doneStatus = function (spec, my) {
+  var that = jobStatus(spec, my);
+  spec = spec || {};
+  my = my || {};
+  // Attributes //
+  // Methods //
+  that.getLabel = function () {
+    return 'done';
+  };
+
+  that.canStart = function () {
+    return false;
+  };
+  that.canRestart = function () {
+    return false;
+  };
+
+  that.isDone = function () {
+    return true;
+  };
+  return that;
+};
+/*jslint indent: 2, maxlen: 80, sloppy: true */
+/*global jobStatus: true */
+var failStatus = function (spec, my) {
+  var that = jobStatus(spec, my);
+  spec = spec || {};
+  my = my || {};
+  // Attributes //
+  // Methods //
+  that.getLabel = function () {
+    return 'fail';
+  };
+
+  that.canStart = function () {
+    return false;
+  };
+  that.canRestart = function () {
+    return true;
+  };
+  return that;
+};
+/*jslint indent: 2, maxlen: 80, sloppy: true */
+/*global jobStatus: true */
+var initialStatus = function (spec, my) {
+  var that = jobStatus(spec, my);
+  spec = spec || {};
+  my = my || {};
+  // Attributes //
+  // Methods //
+  that.getLabel = function () {
+    return "initial";
+  };
+
+  that.canStart = function () {
+    return true;
+  };
+  that.canRestart = function () {
+    return true;
+  };
+  return that;
+};
+/*jslint indent: 2, maxlen: 80, sloppy: true */
+/*global jobStatus: true */
+var jobStatus = function (spec, my) {
+  var that = {};
+  spec = spec || {};
+  my = my || {};
+  // Attributes //
+  // Methods //
+  that.getLabel = function () {
+    return 'job status';
+  };
+
+  that.canStart = function () {};
+  that.canRestart = function () {};
+
+  that.serialized = function () {
+    return {"label": that.getLabel()};
+  };
+
+  that.isWaitStatus = function () {
+    return false;
+  };
+
+  that.isDone = function () {
+    return false;
+  };
+
+  return that;
+};
+/*jslint indent: 2, maxlen: 80, sloppy: true */
+/*global jobStatus: true */
+var onGoingStatus = function (spec, my) {
+  var that = jobStatus(spec, my);
+  spec = spec || {};
+  my = my || {};
+  // Attributes //
+  // Methods //
+  that.getLabel = function () {
+    return 'on going';
+  };
+
+  that.canStart = function () {
+    return false;
+  };
+  that.canRestart = function () {
+    return false;
+  };
+  return that;
+};
+/*jslint indent: 2, maxlen: 80, sloppy: true */
+/*global jobStatus: true, jobManager: true */
+var waitStatus = function (spec, my) {
+  var that = jobStatus(spec, my), priv = {};
+  spec = spec || {};
+  my = my || {};
+  // Attributes //
+  priv.job_id_array = spec.job_id_array || [];
+  priv.threshold = 0;
+
+  // Methods //
+  /**
+   * Returns the label of this status.
+   * @method getLabel
+   * @return {string} The label: 'wait'.
+   */
+  that.getLabel = function () {
+    return 'wait';
+  };
+
+  /**
+   * Refresh the job id array to wait.
+   * @method refreshJobIdArray
+   */
+  priv.refreshJobIdArray = function () {
+    var tmp_job_id_array = [], i;
+    for (i = 0; i < priv.job_id_array.length; i += 1) {
+      if (jobManager.jobIdExists(priv.job_id_array[i])) {
+        tmp_job_id_array.push(priv.job_id_array[i]);
+      }
+    }
+    priv.job_id_array = tmp_job_id_array;
+  };
+
+  /**
+   * The status must wait for the job end before start again.
+   * @method waitForJob
+   * @param  {object} job The job to wait for.
+   */
+  that.waitForJob = function (job) {
+    var i;
+    for (i = 0; i < priv.job_id_array.length; i += 1) {
+      if (priv.job_id_array[i] === job.getId()) {
+        return;
+      }
+    }
+    priv.job_id_array.push(job.getId());
+  };
+
+  /**
+   * The status stops to wait for this job.
+   * @method dontWaitForJob
+   * @param  {object} job The job to stop waiting for.
+   */
+  that.dontWaitForJob = function (job) {
+    var i, tmp_job_id_array = [];
+    for (i = 0; i < priv.job_id_array.length; i += 1) {
+      if (priv.job_id_array[i] !== job.getId()) {
+        tmp_job_id_array.push(priv.job_id_array[i]);
+      }
+    }
+    priv.job_id_array = tmp_job_id_array;
+  };
+
+  /**
+   * The status must wait for some milliseconds.
+   * @method waitForTime
+   * @param  {number} ms The number of milliseconds
+   */
+  that.waitForTime = function (ms) {
+    priv.threshold = Date.now() + ms;
+  };
+
+  /**
+   * The status stops to wait for some time.
+   * @method stopWaitForTime
+   */
+  that.stopWaitForTime = function () {
+    priv.threshold = 0;
+  };
+
+  that.canStart = function () {
+    priv.refreshJobIdArray();
+    return (priv.job_id_array.length === 0 && Date.now() >= priv.threshold);
+  };
+  that.canRestart = function () {
+    return that.canStart();
+  };
+
+  that.serialized = function () {
+    return {
+      "label": that.getLabel(),
+      "waitfortime": priv.threshold,
+      "waitforjob": priv.job_id_array
+    };
+  };
+
+  /**
+   * Checks if this status is waitStatus
+   * @method isWaitStatus
+   * @return {boolean} true
+   */
+  that.isWaitStatus = function () {
+    return true;
+  };
+
+  return that;
+};
+/*jslint indent: 2, maxlen: 80, sloppy: true */
+/*global jobIdHandler: true, initialStatus: true, invalidJobException: true,
+waitStatus: true, failStatus: true, tooMuchTriesJobException: true,
+jobManager: true, jobNotReadyException: true, onGoingStatus: true */
+var job = function (spec) {
+  var that = {},
+    priv = {};
+
+  spec = spec || {};
+
+  priv.id = jobIdHandler.nextId();
+  priv.command = spec.command;
+  priv.storage = spec.storage;
+  priv.status = initialStatus();
+  priv.date = new Date();
+
+  // Initialize //
+  if (!priv.storage) {
+    throw invalidJobException({
+      job: that,
+      message: 'No storage set'
+    });
+  }
+  if (!priv.command) {
+    throw invalidJobException({
+      job: that,
+      message: 'No command set'
+    });
+  }
+  // Methods //
+  /**
+  * Returns the job command.
+  * @method getCommand
+  * @return {object} The job command.
+  */
+  that.getCommand = function () {
+    return priv.command;
+  };
+
+  that.getStatus = function () {
+    return priv.status;
+  };
+
+  that.getId = function () {
+    return priv.id;
+  };
+
+  that.getStorage = function () {
+    return priv.storage;
+  };
+
+  that.getDate = function () {
+    return priv.date;
+  };
+
+  /**
+  * Checks if the job is ready.
+  * @method isReady
+  * @return {boolean} true if ready, else false.
+  */
+  that.isReady = function () {
+    if (priv.command.getTried() === 0) {
+      return priv.status.canStart();
+    }
+    return priv.status.canRestart();
+  };
+
+  /**
+    * Returns a serialized version of this job.
+    * @method serialized
+    * @return {object} The serialized job.
+    */
+  that.serialized = function () {
+    return {
+      id: priv.id,
+      date: priv.date.getTime(),
+      status: priv.status.serialized(),
+      command: priv.command.serialized(),
+      storage: priv.storage.serialized()
+    };
+  };
+
+  /**
+  * Tells the job to wait for another one.
+  * @method waitForJob
+  * @param  {object} job The job to wait for.
+  */
+  that.waitForJob = function (job) {
+    if (priv.status.getLabel() !== 'wait') {
+      priv.status = waitStatus({});
+    }
+    priv.status.waitForJob(job);
+  };
+
+  /**
+  * Tells the job to do not wait for a job.
+  * @method dontWaitForJob
+  * @param  {object} job The other job.
+  */
+  that.dontWaitFor = function (job) {
+    if (priv.status.getLabel() === 'wait') {
+      priv.status.dontWaitForJob(job);
+    }
+  };
+
+  /**
+  * Tells the job to wait for a while.
+  * @method waitForTime
+  * @param  {number} ms Time to wait in millisecond.
+  */
+  that.waitForTime = function (ms) {
+    if (priv.status.getLabel() !== 'wait') {
+      priv.status = waitStatus({});
+    }
+    priv.status.waitForTime(ms);
+  };
+
+  /**
+  * Tells the job to do not wait for a while anymore.
+  * @method stopWaitForTime
+  */
+  that.stopWaitForTime = function () {
+    if (priv.status.getLabel() === 'wait') {
+      priv.status.stopWaitForTime();
+    }
+  };
+
+  that.eliminated = function () {
+    priv.command.error({
+      status: 10,
+      statusText: 'Stopped',
+      error: 'stopped',
+      message: 'This job has been stopped by another one.',
+      reason: 'this job has been stopped by another one'
+    });
+  };
+
+  that.notAccepted = function () {
+    priv.command.onEndDo(function () {
+      priv.status = failStatus();
+      jobManager.terminateJob(that);
+    });
+    priv.command.error({
+      status: 11,
+      statusText: 'Not Accepted',
+      error: 'not_accepted',
+      message: 'This job is already running.',
+      reason: 'this job is already running'
+    });
+  };
+
+  /**
+  * Updates the date of the job with the another one.
+  * @method update
+  * @param  {object} job The other job.
+  */
+  that.update = function (job) {
+    priv.command.error({
+      status: 12,
+      statusText: 'Replaced',
+      error: 'replaced',
+      message: 'Job has been replaced by another one.',
+      reason: 'job has been replaced by another one'
+    });
+    priv.date = new Date(job.getDate().getTime());
+    priv.command = job.getCommand();
+    priv.status = job.getStatus();
+  };
+
+  /**
+  * Executes this job.
+  * @method execute
+  */
+  that.execute = function () {
+    if (!that.getCommand().canBeRetried()) {
+      throw tooMuchTriesJobException({
+        job: that,
+        message: 'The job was invoked too much time.'
+      });
+    }
+    if (!that.isReady()) {
+      throw jobNotReadyException({
+        job: that,
+        message: 'Can not execute this job.'
+      });
+    }
+    priv.status = onGoingStatus();
+    priv.command.onRetryDo(function () {
+      var ms = priv.command.getTried();
+      ms = ms * ms * 200;
+      if (ms > 10000) {
+        ms = 10000;
+      }
+      that.waitForTime(ms);
+    });
+    priv.command.onEndDo(function (status) {
+      priv.status = status;
+      jobManager.terminateJob(that);
+    });
+    priv.command.execute(priv.storage);
+  };
+  return that;
+};
+/*jslint indent: 2, maxlen: 80, sloppy: true */
+/*global announcement: true */
+var announcement = function (spec, my) {
+  var that = {},
+    callback_a = [],
+    announcer = spec.announcer || {};
+
+  spec = spec || {};
+  my = my || {};
+
+  // Methods //
+  that.add = function (callback) {
+    callback_a.push(callback);
+  };
+
+  that.remove = function (callback) {
+    var i, tmp_callback_a = [];
+    for (i = 0; i < callback_a.length; i += 1) {
+      if (callback_a[i] !== callback) {
+        tmp_callback_a.push(callback_a[i]);
+      }
+    }
+    callback_a = tmp_callback_a;
+  };
+
+  that.register = function () {
+    announcer.register(that);
+  };
+
+  that.unregister = function () {
+    announcer.unregister(that);
+  };
+
+  that.trigger = function (args) {
+    var i;
+    for (i = 0; i < callback_a.length; i += 1) {
+      callback_a[i].apply(null, args);
+    }
+  };
+
+  return that;
+};
+/*jslint indent: 2, maxlen: 80, sloppy: true */
+/*global localstorage: true, setInterval: true, clearInterval: true */
+var activityUpdater = (function (spec, my) {
+  var that = {}, priv = {};
+  spec = spec || {};
+  my = my || {};
+
+  priv.id = spec.id || 0;
+  priv.interval = 400;
+  priv.interval_id = null;
+
+  // Methods //
+  /**
+   * Update the last activity date in the localStorage.
+   * @method touch
+   */
+  priv.touch = function () {
+    localstorage.setItem('jio/id/' + priv.id, Date.now());
+  };
+
+  /**
+   * Sets the jio id into the activity.
+   * @method setId
+   * @param  {number} id The jio id.
+   */
+  that.setId = function (id) {
+    priv.id = id;
+  };
+
+  /**
+   * Sets the interval delay between two updates.
+   * @method setIntervalDelay
+   * @param  {number} ms In milliseconds
+   */
+  that.setIntervalDelay = function (ms) {
+    priv.interval = ms;
+  };
+
+  /**
+   * Gets the interval delay.
+   * @method getIntervalDelay
+   * @return {number} The interval delay.
+   */
+  that.getIntervalDelay = function () {
+    return priv.interval;
+  };
+
+  /**
+   * Starts the activity updater. It will update regulary the last activity
+   * date in the localStorage to show to other jio instance that this instance
+   * is active.
+   * @method start
+   */
+  that.start = function () {
+    if (!priv.interval_id) {
+      priv.touch();
+      priv.interval_id = setInterval(function () {
+        priv.touch();
+      }, priv.interval);
+    }
+  };
+
+  /**
+   * Stops the activity updater.
+   * @method stop
+   */
+  that.stop = function () {
+    if (priv.interval_id !== null) {
+      clearInterval(priv.interval_id);
+      priv.interval_id = null;
+    }
+  };
+
+  return that;
+}());
+/*jslint indent: 2, maxlen: 80, sloppy: true */
+/*global announcement: true */
+var announcer = (function (spec, my) {
+  var that = {},
+    announcement_o = {};
+  spec = spec || {};
+  my = my || {};
+
+  // Methods //
+  that.register = function (name) {
+    if (!announcement_o[name]) {
+      announcement_o[name] = announcement();
+    }
+  };
+
+  that.unregister = function (name) {
+    if (announcement_o[name]) {
+      delete announcement_o[name];
+    }
+  };
+
+  that.at = function (name) {
+    return announcement_o[name];
+  };
+
+  that.on = function (name, callback) {
+    that.register(name);
+    that.at(name).add(callback);
+  };
+
+  that.trigger = function (name, args) {
+    that.at(name).trigger(args);
+  };
+
+  return that;
+}());
+/*jslint indent: 2, maxlen: 80, sloppy: true */
+var jobIdHandler = (function (spec) {
+  var that = {},
+    id = 0;
+  spec = spec || {};
+
+  // Methods //
+  that.nextId = function () {
+    id = id + 1;
+    return id;
+  };
+
+  return that;
+}());
+/*jslint indent: 2, maxlen: 80, sloppy: true */
+/*global localstorage: true, setInterval: true, clearInterval: true,
+ command: true, job: true, jobRules: true */
+var jobManager = (function (spec) {
+  var that = {},
+    job_array_name = 'jio/job_array',
+    priv = {};
+
+  spec = spec || {};
+  // Attributes //
+  priv.id = spec.id;
+  priv.interval_id = null;
+  priv.interval = 200;
+  priv.job_array = [];
+
+  // Methods //
+  /**
+  * Get the job array name in the localStorage
+  * @method getJobArrayName
+  * @return {string} The job array name
+  */
+  priv.getJobArrayName = function () {
+    return job_array_name + '/' + priv.id;
+  };
+
+  /**
+  * Returns the job array from the localStorage
+  * @method getJobArray
+  * @return {array} The job array.
+  */
+  priv.getJobArray = function () {
+    return localstorage.getItem(priv.getJobArrayName()) || [];
+  };
+
+  /**
+  * Does a backup of the job array in the localStorage.
+  * @method copyJobArrayToLocal
+  */
+  priv.copyJobArrayToLocal = function () {
+    var new_a = [],
+      i;
+    for (i = 0; i < priv.job_array.length; i += 1) {
+      new_a.push(priv.job_array[i].serialized());
+    }
+    localstorage.setItem(priv.getJobArrayName(), new_a);
+  };
+
+  /**
+  * Removes a job from the current job array.
+  * @method removeJob
+  * @param  {object} job The job object.
+  */
+  priv.removeJob = function (job) {
+    var i,
+      tmp_job_array = [];
+    for (i = 0; i < priv.job_array.length; i += 1) {
+      if (priv.job_array[i] !== job) {
+        tmp_job_array.push(priv.job_array[i]);
+      }
+    }
+    priv.job_array = tmp_job_array;
+    priv.copyJobArrayToLocal();
+  };
+
+  /**
+  * Sets the job manager id.
+  * @method setId
+  * @param  {number} id The id.
+  */
+  that.setId = function (id) {
+    priv.id = id;
+  };
+
+  /**
+  * Starts listening to the job array, executing them regulary.
+  * @method start
+  */
+  that.start = function () {
+    var i;
+    if (priv.interval_id === null) {
+      priv.interval_id = setInterval(function () {
+        priv.restoreOldJio();
+        for (i = 0; i < priv.job_array.length; i += 1) {
+          that.execute(priv.job_array[i]);
+        }
+      }, priv.interval);
+    }
+  };
+
+  /**
+  * Stops listening to the job array.
+  * @method stop
+  */
+  that.stop = function () {
+    if (priv.interval_id !== null) {
+      clearInterval(priv.interval_id);
+      priv.interval_id = null;
+      if (priv.job_array.length === 0) {
+        localstorage.removeItem(priv.getJobArrayName());
+      }
+    }
+  };
+
+  /**
+  * Try to restore an the inactive older jio instances.
+  * It will restore the on going or initial jobs from their job array
+  * and it will add them to this job array.
+  * @method restoreOldJio
+  */
+  priv.restoreOldJio = function () {
+    var i,
+      jio_id_a;
+    priv.lastrestore = priv.lastrestore || 0;
+    if (priv.lastrestore > (Date.now()) - 2000) {
+      return;
+    }
+    jio_id_a = localstorage.getItem('jio/id_array') || [];
+    for (i = 0; i < jio_id_a.length; i += 1) {
+      priv.restoreOldJioId(jio_id_a[i]);
+    }
+    priv.lastrestore = Date.now();
+  };
+
+  /**
+  * Try to restore an old jio according to an id.
+  * @method restoreOldJioId
+  * @param  {number} id The jio id.
+  */
+  priv.restoreOldJioId = function (id) {
+    var jio_date;
+    jio_date = localstorage.getItem('jio/id/' + id) || 0;
+    if (new Date(jio_date).getTime() < (Date.now() - 10000)) { // 10 sec
+      priv.restoreOldJobFromJioId(id);
+      priv.removeOldJioId(id);
+      priv.removeJobArrayFromJioId(id);
+    }
+  };
+
+  /**
+  * Try to restore all jobs from another jio according to an id.
+  * @method restoreOldJobFromJioId
+  * @param  {number} id The jio id.
+  */
+  priv.restoreOldJobFromJioId = function (id) {
+    var i,
+      command_object,
+      jio_job_array;
+    jio_job_array = localstorage.getItem('jio/job_array/' + id) || [];
+    for (i = 0; i < jio_job_array.length; i += 1) {
+      command_object = command(jio_job_array[i].command);
+      if (command_object.canBeRestored()) {
+        that.addJob(job({
+          storage: that.storage(jio_job_array[i].storage),
+          command: command_object
+        }));
+      }
+    }
+  };
+  /**
+  * Removes a jio instance according to an id.
+  * @method removeOldJioId
+  * @param  {number} id The jio id.
+  */
+  priv.removeOldJioId = function (id) {
+    var i,
+      jio_id_array,
+      new_array = [];
+    jio_id_array = localstorage.getItem('jio/id_array') || [];
+    for (i = 0; i < jio_id_array.length; i += 1) {
+      if (jio_id_array[i] !== id) {
+        new_array.push(jio_id_array[i]);
+      }
+    }
+    localstorage.setItem('jio/id_array', new_array);
+    localstorage.removeItem('jio/id/' + id);
+  };
+  /**
+  * Removes a job array from a jio instance according to an id.
+  * @method removeJobArrayFromJioId
+  * @param  {number} id The jio id.
+  */
+  priv.removeJobArrayFromJioId = function (id) {
+    localstorage.removeItem('jio/job_array/' + id);
+  };
+  /**
+  * Executes a job.
+  * @method execute
+  * @param  {object} job The job object.
+  */
+  that.execute = function (job) {
+    try {
+      job.execute();
+    } catch (e) {
+      switch (e.name) {
+      case 'jobNotReadyException':
+        break; // do nothing
+      case 'tooMuchTriesJobException':
+        break; // do nothing
+      default:
+        throw e;
+      }
+    }
+    priv.copyJobArrayToLocal();
+  };
+  /**
+  * Checks if a job exists in the job array according to a job id.
+  * @method jobIdExists
+  * @param  {number} id The job id.
+  * @return {boolean} true if exists, else false.
+  */
+  that.jobIdExists = function (id) {
+    var i;
+    for (i = 0; i < priv.job_array.length; i += 1) {
+      if (priv.job_array[i].getId() === id) {
+        return true;
+      }
+    }
+    return false;
+  };
+  /**
+  * Terminate a job. It only remove it from the job array.
+  * @method terminateJob
+  * @param  {object} job The job object
+  */
+  that.terminateJob = function (job) {
+    priv.removeJob(job);
+  };
+  /**
+  * Adds a job to the current job array.
+  * @method addJob
+  * @param  {object} job The new job.
+  */
+  that.addJob = function (job) {
+    var result_array = that.validateJobAccordingToJobList(priv.job_array, job);
+    priv.appendJob(job, result_array);
+  };
+  /**
+  * Generate a result array containing action string to do with the good job.
+  * @method validateJobAccordingToJobList
+  * @param  {array} job_array A job array.
+  * @param  {object} job The new job to compare with.
+  * @return {array} A result array.
+  */
+  that.validateJobAccordingToJobList = function (job_array, job) {
+    var i,
+      result_array = [];
+    for (i = 0; i < job_array.length; i += 1) {
+      result_array.push(jobRules.validateJobAccordingToJob(job_array[i], job));
+    }
+    return result_array;
+  };
+  /**
+  * It will manage the job in order to know what to do thanks to a result
+  * array. The new job can be added to the job array, but it can also be
+  * not accepted. It is this method which can tells jobs to wait for another
+  * one, to replace one or to eliminate some while browsing.
+  * @method appendJob
+  * @param  {object} job The job to append.
+  * @param  {array} result_array The result array.
+  */
+  priv.appendJob = function (job, result_array) {
+    var i;
+    if (priv.job_array.length !== result_array.length) {
+      throw new RangeError("Array out of bound");
+    }
+    for (i = 0; i < result_array.length; i += 1) {
+      if (result_array[i].action === 'dont accept') {
+        return job.notAccepted();
+      }
+    }
+    for (i = 0; i < result_array.length; i += 1) {
+      switch (result_array[i].action) {
+      case 'eliminate':
+        result_array[i].job.eliminated();
+        priv.removeJob(result_array[i].job);
+        break;
+      case 'update':
+        result_array[i].job.update(job);
+        priv.copyJobArrayToLocal();
+        return;
+      case 'wait':
+        job.waitForJob(result_array[i].job);
+        break;
+      default:
+        break;
+      }
+    }
+    priv.job_array.push(job);
+    priv.copyJobArrayToLocal();
+  };
+  that.serialized = function () {
+    var a = [],
+      i,
+      job_array = priv.job_array || [];
+    for (i = 0; i < job_array.length; i += 1) {
+      a.push(job_array[i].serialized());
+    }
+    return a;
+  };
+  return that;
+}());
+/*jslint indent: 2, maxlen: 80, sloppy: true */
+var jobRules = (function () {
+  var that = {}, priv = {};
+
+  priv.compare = {};
+  priv.action = {};
+
+  Object.defineProperty(that, "eliminate", {
+    configurable: false,
+    enumerable: false,
+    writable: false,
+    value: function () {
+      return 'eliminate';
+    }
+  });
+  Object.defineProperty(that, "update", {
+    configurable: false,
+    enumerable: false,
+    writable: false,
+    value: function () {
+      return 'update';
+    }
+  });
+  Object.defineProperty(that, "dontAccept", {
+    configurable: false,
+    enumerable: false,
+    writable: false,
+    value: function () {
+      return 'dont accept';
+    }
+  });
+  Object.defineProperty(that, "wait", {
+    configurable: false,
+    enumerable: false,
+    writable: false,
+    value: function () {
+      return 'wait';
+    }
+  });
+  Object.defineProperty(that, "none", {
+    configurable: false,
+    enumerable: false,
+    writable: false,
+    value: function () {
+      return 'none';
+    }
+  });
+  that.default_action = that.none;
+  that.default_compare = function (job1, job2) {
+    return (job1.getCommand().getDocId() === job2.getCommand().getDocId() &&
+    job1.getCommand().getDocInfo('_rev') ===
+    job2.getCommand().getDocInfo('_rev') &&
+    job1.getCommand().getOption('rev') ===
+    job2.getCommand().getOption('rev') &&
+    JSON.stringify(job1.getStorage().serialized()) ===
+    JSON.stringify(job2.getStorage().serialized()));
+  };
+
+  // Methods //
+  /**
+   * Returns an action according the jobs given in parameters.
+   * @method getAction
+   * @param  {object} job1 The already existant job.
+   * @param  {object} job2 The job to compare with.
+   * @return {string} An action string.
+   */
+  priv.getAction = function (job1, job2) {
+    var j1label, j2label, j1status;
+    j1label = job1.getCommand().getLabel();
+    j2label = job2.getCommand().getLabel();
+    j1status = (job1.getStatus().getLabel() === 'on going' ?
+        'on going' : 'not on going');
+    if (priv.action[j1label] && priv.action[j1label][j1status] &&
+        priv.action[j1label][j1status][j2label]) {
+      return priv.action[j1label][j1status][j2label](job1, job2);
+    }
+    return that.default_action(job1, job2);
+  };
+
+  /**
+   * Checks if the two jobs are comparable.
+   * @method canCompare
+   * @param  {object} job1 The already existant job.
+   * @param  {object} job2 The job to compare with.
+   * @return {boolean} true if comparable, else false.
+   */
+  priv.canCompare = function (job1, job2) {
+    var job1label = job1.getCommand().getLabel(),
+      job2label = job2.getCommand().getLabel();
+    if (priv.compare[job1label] && priv.compare[job2label]) {
+      return priv.compare[job1label][job2label](job1, job2);
+    }
+    return that.default_compare(job1, job2);
+  };
+
+  /**
+   * Returns an action string to show what to do if we want to add a job.
+   * @method validateJobAccordingToJob
+   * @param  {object} job1 The current job.
+   * @param  {object} job2 The new job.
+   * @return {string} The action string.
+   */
+  Object.defineProperty(that, "validateJobAccordingToJob", {
+    configurable: false,
+    enumerable: false,
+    writable: false,
+    value: function (job1, job2) {
+      if (priv.canCompare(job1, job2)) {
+        return {
+          action: priv.getAction(job1, job2),
+          job: job1
+        };
+      }
+      return {
+        action: that.default_action(job1, job2),
+        job: job1
+      };
+    }
+  });
+
+  /**
+   * Adds a rule the action rules.
+   * @method addActionRule
+   * @param {string} method1 The action label from the current job.
+   * @param {boolean} ongoing Is this action is on going or not?
+   * @param {string} method2 The action label from the new job.
+   * @param {function} rule The rule that return an action string.
+   */
+  Object.defineProperty(that, "addActionRule", {
+    configurable: false,
+    enumerable: false,
+    writable: false,
+    value: function (method1, ongoing, method2, rule) {
+      var ongoing_s = (ongoing ? 'on going' : 'not on going');
+      priv.action[method1] = priv.action[method1] || {};
+      priv.action[method1][ongoing_s] = priv.action[method1][ongoing_s] || {};
+      priv.action[method1][ongoing_s][method2] = rule;
+    }
+  });
+
+  /**
+   * Adds a rule the compare rules.
+   * @method addCompareRule
+   * @param {string} method1 The action label from the current job.
+   * @param {string} method2 The action label from the new job.
+   * @param {function} rule The rule that return a boolean
+   * - true if job1 and job2 can be compared, else false.
+   */
+  Object.defineProperty(that, "addCompareRule", {
+    configurable: false,
+    enumerable: false,
+    writable: false,
+    value: function (method1, method2, rule) {
+      priv.compare[method1] = priv.compare[method1] || {};
+      priv.compare[method1][method2] = rule;
+    }
+  });
+
+  ////////////////////////////////////////////////////////////////////////////
+  // Adding some rules
+  /*
+      LEGEND:
+      - s: storage
+      - m: method
+      - n: name
+      - c: content
+      - o: options
+      - =: are equal
+      - !: are not equal
+
+      select ALL        s= n=
+      removefailordone  fail|done
+      /                           elim repl nacc wait
+      Remove     !ongoing  Save    1    x    x    x
+      Save       !ongoing  Remove  1    x    x    x
+      GetList    !ongoing  GetList 0    1    x    x
+      Remove     !ongoing  Remove  0    1    x    x
+      Load       !ongoing  Load    0    1    x    x
+      Save c=    !ongoing  Save    0    1    x    x
+      Save c!    !ongoing  Save    0    1    x    x
+      GetList     ongoing  GetList 0    0    1    x
+      Remove      ongoing  Remove  0    0    1    x
+      Remove      ongoing  Load    0    0    1    x
+      Remove     !ongoing  Load    0    0    1    x
+      Load        ongoing  Load    0    0    1    x
+      Save c=     ongoing  Save    0    0    1    x
+      Remove      ongoing  Save    0    0    0    1
+      Load        ongoing  Remove  0    0    0    1
+      Load        ongoing  Save    0    0    0    1
+      Load       !ongoing  Remove  0    0    0    1
+      Load       !ongoing  Save    0    0    0    1
+      Save        ongoing  Remove  0    0    0    1
+      Save        ongoing  Load    0    0    0    1
+      Save c!     ongoing  Save    0    0    0    1
+      Save       !ongoing  Load    0    0    0    1
+      GetList     ongoing  Remove  0    0    0    0
+      GetList     ongoing  Load    0    0    0    0
+      GetList     ongoing  Save    0    0    0    0
+      GetList    !ongoing  Remove  0    0    0    0
+      GetList    !ongoing  Load    0    0    0    0
+      GetList    !ongoing  Save    0    0    0    0
+      Remove      ongoing  GetList 0    0    0    0
+      Remove     !ongoing  GetList 0    0    0    0
+      Load        ongoing  GetList 0    0    0    0
+      Load       !ongoing  GetList 0    0    0    0
+      Save        ongoing  GetList 0    0    0    0
+      Save       !ongoing  GetList 0    0    0    0
+
+      For more information, see documentation
+    */
+  that.addActionRule('post', true, 'post', that.dontAccept);
+  that.addActionRule('post', true, 'put', that.wait);
+  that.addActionRule('post', true, 'get', that.wait);
+  that.addActionRule('post', true, 'remove', that.wait);
+  that.addActionRule('post', true, 'putAttachment', that.wait);
+  that.addActionRule('post', false, 'post', that.update);
+  that.addActionRule('post', false, 'put', that.wait);
+  that.addActionRule('post', false, 'get', that.wait);
+  that.addActionRule('post', false, 'remove', that.eliminate);
+  that.addActionRule('post', false, 'putAttachment', that.wait);
+
+  that.addActionRule('put', true, 'post', that.dontAccept);
+  that.addActionRule('put', true, 'put', that.wait);
+  that.addActionRule('put', true, 'get', that.wait);
+  that.addActionRule('put', true, 'remove', that.wait);
+  that.addActionRule('put', true, 'putAttachment', that.wait);
+  that.addActionRule('put', false, 'post', that.dontAccept);
+  that.addActionRule('put', false, 'put', that.update);
+  that.addActionRule('put', false, 'get', that.wait);
+  that.addActionRule('put', false, 'remove', that.eliminate);
+  that.addActionRule('put', false, 'putAttachment', that.wait);
+
+  that.addActionRule('get', true, 'post', that.wait);
+  that.addActionRule('get', true, 'put', that.wait);
+  that.addActionRule('get', true, 'get', that.dontAccept);
+  that.addActionRule('get', true, 'remove', that.wait);
+  that.addActionRule('get', true, 'putAttachment', that.wait);
+  that.addActionRule('get', false, 'post', that.wait);
+  that.addActionRule('get', false, 'put', that.wait);
+  that.addActionRule('get', false, 'get', that.update);
+  that.addActionRule('get', false, 'remove', that.wait);
+  that.addActionRule('get', false, 'putAttachment', that.wait);
+
+  that.addActionRule('remove', true, 'post', that.wait);
+  that.addActionRule('remove', true, 'get', that.dontAccept);
+  that.addActionRule('remove', true, 'remove', that.dontAccept);
+  that.addActionRule('remove', true, 'putAttachment', that.dontAccept);
+  that.addActionRule('remove', false, 'post', that.eliminate);
+  that.addActionRule('remove', false, 'put', that.dontAccept);
+  that.addActionRule('remove', false, 'get', that.dontAccept);
+  that.addActionRule('remove', false, 'remove', that.update);
+  that.addActionRule('remove', false, 'putAttachment', that.dontAccept);
+
+  that.addActionRule('allDocs', true, 'allDocs', that.dontAccept);
+  that.addActionRule('allDocs', false, 'allDocs', that.update);
+
+  that.addActionRule('putAttachment', true, 'post', that.dontAccept);
+  that.addActionRule('putAttachment', true, 'put', that.wait);
+  that.addActionRule('putAttachment', true, 'get', that.wait);
+  that.addActionRule('putAttachment', true, 'remove', that.wait);
+  that.addActionRule('putAttachment', true, 'putAttachment', that.wait);
+  that.addActionRule('putAttachment', false, 'post', that.dontAccept);
+  that.addActionRule('putAttachment', false, 'put', that.wait);
+  that.addActionRule('putAttachment', false, 'get', that.wait);
+  that.addActionRule('putAttachment', false, 'remove', that.eliminate);
+  that.addActionRule('putAttachment', false, 'putAttachment', that.update);
+  // end adding rules
+  ////////////////////////////////////////////////////////////////////////////
+  return that;
+}());
+/*jslint indent: 2, maxlen: 80, sloppy: true */
+/*global spec: true, localstorage: true,
+         activityUpdater: true, jobManager: true, storage: true,
+         storage_type_object: true, invalidStorageType: true, jobRules: true,
+         job: true, postCommand: true, putCommand: true, getCommand:true,
+         allDocsCommand: true, putAttachmentCommand: true,
+         removeCommand: true */
+// Class jio
+var that = {}, priv = {}, jio_id_array_name = 'jio/id_array';
+spec = spec || {};
+// Attributes //
+priv.id = null;
+
+priv.storage_spec = spec;
+
+priv.environments = {};
+
+// initialize //
+priv.init = function () {
+  // Initialize the jio id and add the new id to the list
+  if (priv.id === null) {
+    var i, jio_id_a =
+      localstorage.getItem(jio_id_array_name) || [];
+    priv.id = 1;
+    for (i = 0; i < jio_id_a.length; i += 1) {
+      if (jio_id_a[i] >= priv.id) {
+        priv.id = jio_id_a[i] + 1;
+      }
+    }
+    jio_id_a.push(priv.id);
+    localstorage.setItem(jio_id_array_name, jio_id_a);
+    activityUpdater.setId(priv.id);
+    jobManager.setId(priv.id);
+  }
+};
+
+// Methods //
+/**
+ * Returns a storage from a storage description.
+ * @method storage
+ * @param  {object} spec The specifications.
+ * @param  {object} my The protected object.
+ * @param  {string} forcetype Force storage type
+ * @return {object} The storage object.
+ */
+Object.defineProperty(that, "storage", {
+  configurable: false,
+  enumerable: false,
+  writable: false,
+  value: function (spec, my, forcetype) {
+    var spec_str, type;
+    spec = spec || {};
+    my = my || {};
+    my.basicStorage = storage;
+    spec_str = JSON.stringify(spec);
+    // environment initialization
+    priv.environments[spec_str] = priv.environments[spec_str] || {};
+    my.env = priv.environments[spec_str];
+    my.storage = that.storage; // NOTE : or proxy storage
+    type = forcetype || spec.type || 'base';
+    if (type === 'base') {
+      return storage(spec, my);
+    }
+    if (!storage_type_object[type]) {
+      throw invalidStorageType({
+        "type": type,
+        "message": "Storage does not exists."
+      });
+    }
+    return storage_type_object[type](spec, my);
+  }
+});
+jobManager.storage = that.storage;
+
+Object.defineProperty(that, "start", {
+  configurable: false,
+  enumerable: false,
+  writable: false,
+  value: function () {
+    priv.init();
+    activityUpdater.start();
+    jobManager.start();
+  }
+});
+
+Object.defineProperty(that, "stop", {
+  configurable: false,
+  enumerable: false,
+  writable: false,
+  value: function () {
+    jobManager.stop();
+  }
+});
+
+Object.defineProperty(that, "close", {
+  configurable: false,
+  enumerable: false,
+  writable: false,
+  value: function () {
+    activityUpdater.stop();
+    jobManager.stop();
+    priv.id = null;
+  }
+});
+
+/**
+ * Returns the jio id.
+ * @method getId
+ * @return {number} The jio id.
+ */
+Object.defineProperty(that, "getId", {
+  configurable: false,
+  enumerable: false,
+  writable: false,
+  value: function () {
+    return priv.id;
+  }
+});
+
+/**
+ * Returns the jio job rules object used by the job manager.
+ * @method getJobRules
+ * @return {object} The job rules object
+ */
+Object.defineProperty(that, "getJobRules", {
+  configurable: false,
+  enumerable: false,
+  writable: false,
+  value: function () {
+    return jobRules;
+  }
+});
+
+/**
+ * Checks if the storage description is valid or not.
+ * @method validateStorageDescription
+ * @param  {object} description The description object.
+ * @return {boolean} true if ok, else false.
+ */
+Object.defineProperty(that, "validateStorageDescription", {
+  configurable: false,
+  enumerable: false,
+  writable: false,
+  value: function (description) {
+    return that.storage(description).isValid();
+  }
+});
+
+Object.defineProperty(that, "getJobArray", {
+  configurable: false,
+  enumerable: false,
+  writable: false,
+  value: function () {
+    return jobManager.serialized();
+  }
+});
+
+priv.makeCallbacks = function (param, callback1, callback2) {
+  param.callback = function (err, val) {
+    if (err) {
+      param.error(err);
+    } else {
+      param.success(val);
+    }
+  };
+  param.success = function (val) {
+    param.callback(undefined, val);
+  };
+  param.error = function (err) {
+    param.callback(err, undefined);
+  };
+  if (typeof callback1 === 'function') {
+    if (typeof callback2 === 'function') {
+      param.success = callback1;
+      param.error = callback2;
+    } else {
+      param.callback = callback1;
+    }
+  } else {
+    param.callback = function () {};
+  }
+};
+
+priv.parametersToObject = function (list, default_options) {
+  var k, i = 0, callbacks = [], param = {"options": {}};
+  for (i = 0; i < list.length; i += 1) {
+    if (typeof list[i] === 'object') {
+      // this is the option
+      param.options = list[i];
+      for (k in default_options) {
+        if ((typeof default_options[k]) !== (typeof list[i][k])) {
+          param.options[k] = default_options[k];
+        }
+      }
+    }
+    if (typeof list[i] === 'function') {
+      // this is a callback
+      callbacks.push(list[i]);
+    }
+  }
+  priv.makeCallbacks(param, callbacks[0], callbacks[1]);
+  return param;
+};
+
+priv.addJob = function (commandCreator, spec) {
+  jobManager.addJob(job({
+    "storage": that.storage(priv.storage_spec),
+    "command": commandCreator(spec)
+  }));
+};
+
+/**
+ * Post a document.
+ * @method post
+ * @param  {object} doc The document object. Contains at least:
+ * - {string} _id The document id (optional), "/" are forbidden
+ * @param  {object} options (optional) Contains some options:
+ * - {number} max_retry The number max of retries, 0 = infinity.
+ * - {boolean} revs Include revision history of the document.
+ * - {boolean} revs_info Retreive the revisions.
+ * - {boolean} conflicts Retreive the conflict list.
+ * @param  {function} callback (optional) The callback(err,response).
+ * @param  {function} error (optional) The callback on error, if this
+ *     callback is given in parameter, "callback" is changed as "success",
+ *     called on success.
+ */
+Object.defineProperty(that, "post", {
+  configurable: false,
+  enumerable: false,
+  writable: false,
+  value: function (doc, options, success, error) {
+    var param = priv.parametersToObject(
+      [options, success, error],
+      {max_retry: 0}
+    );
+
+    priv.addJob(postCommand, {
+      doc: doc,
+      options: param.options,
+      callbacks: {success: param.success, error: param.error}
+    });
+  }
+});
+
+/**
+ * Put a document.
+ * @method put
+ * @param  {object} doc The document object. Contains at least:
+ * - {string} _id The document id, "/" are forbidden
+ * @param  {object} options (optional) Contains some options:
+ * - {number} max_retry The number max of retries, 0 = infinity.
+ * - {boolean} revs Include revision history of the document.
+ * - {boolean} revs_info Retreive the revisions.
+ * - {boolean} conflicts Retreive the conflict list.
+ * @param  {function} callback (optional) The callback(err,response).
+ * @param  {function} error (optional) The callback on error, if this
+ *     callback is given in parameter, "callback" is changed as "success",
+ *     called on success.
+ */
+Object.defineProperty(that, "put", {
+  configurable: false,
+  enumerable: false,
+  writable: false,
+  value: function (doc, options, success, error) {
+    var param = priv.parametersToObject(
+      [options, success, error],
+      {max_retry: 0}
+    );
+
+    priv.addJob(putCommand, {
+      doc: doc,
+      options: param.options,
+      callbacks: {success: param.success, error: param.error}
+    });
+  }
+});
+
+/**
+ * Get a document.
+ * @method get
+ * @param  {string} docid The document id: "doc_id" or "doc_id/attachmt_id".
+ * @param  {object} options (optional) Contains some options:
+ * - {number} max_retry The number max of retries, 0 = infinity.
+ * - {string} rev The revision we want to get.
+ * - {boolean} revs Include revision history of the document.
+ * - {boolean} revs_info Include list of revisions, and their availability.
+ * - {boolean} conflicts Include a list of conflicts.
+ * @param  {function} callback (optional) The callback(err,response).
+ * @param  {function} error (optional) The callback on error, if this
+ *     callback is given in parameter, "callback" is changed as "success",
+ *     called on success.
+ */
+Object.defineProperty(that, "get", {
+  configurable: false,
+  enumerable: false,
+  writable: false,
+  value: function (id, options, success, error) {
+    var param = priv.parametersToObject(
+      [options, success, error],
+      {max_retry: 3}
+    );
+
+    priv.addJob(getCommand, {
+      docid: id,
+      options: param.options,
+      callbacks: {success: param.success, error: param.error}
+    });
+  }
+});
+
+/**
+ * Remove a document.
+ * @method remove
+ * @param  {object} doc The document object. Contains at least:
+ * - {string} _id The document id: "doc_id" or "doc_id/attachment_id"
+ * @param  {object} options (optional) Contains some options:
+ * - {number} max_retry The number max of retries, 0 = infinity.
+ * - {boolean} revs Include revision history of the document.
+ * - {boolean} revs_info Include list of revisions, and their availability.
+ * - {boolean} conflicts Include a list of conflicts.
+ * @param  {function} callback (optional) The callback(err,response).
+ * @param  {function} error (optional) The callback on error, if this
+ *     callback is given in parameter, "callback" is changed as "success",
+ *     called on success.
+ */
+Object.defineProperty(that, "remove", {
+  configurable: false,
+  enumerable: false,
+  writable: false,
+  value: function (doc, options, success, callback) {
+    var param = priv.parametersToObject(
+      [options, success, callback],
+      {max_retry: 0}
+    );
+
+    priv.addJob(removeCommand, {
+      doc: doc,
+      options: param.options,
+      callbacks: {success: param.success, error: param.error}
+    });
+  }
+});
+
+/**
+ * Get a list of documents.
+ * @method allDocs
+ * @param  {object} options (optional) Contains some options:
+ * - {number} max_retry The number max of retries, 0 = infinity.
+ * - {boolean} include_docs Include document metadata
+ * - {boolean} revs Include revision history of the document.
+ * - {boolean} revs_info Include revisions.
+ * - {boolean} conflicts Include conflicts.
+ * @param  {function} callback (optional) The callback(err,response).
+ * @param  {function} error (optional) The callback on error, if this
+ *     callback is given in parameter, "callback" is changed as "success",
+ *     called on success.
+ */
+Object.defineProperty(that, "allDocs", {
+  configurable: false,
+  enumerable: false,
+  writable: false,
+  value: function (options, success, error) {
+    var param = priv.parametersToObject(
+      [options, success, error],
+      {max_retry: 3}
+    );
+
+    priv.addJob(allDocsCommand, {
+      options: param.options,
+      callbacks: {success: param.success, error: param.error}
+    });
+  }
+});
+
+/**
+ * Put an attachment to a document.
+ * @method putAttachment
+ * @param  {object} doc The document object. Contains at least:
+ * - {string} id The document id: "doc_id/attchment_id"
+ * - {string} data Base64 attachment data
+ * - {string} mimetype The attachment mimetype
+ * - {string} rev The attachment revision
+ * @param  {object} options (optional) Contains some options:
+ * - {number} max_retry The number max of retries, 0 = infinity.
+ * - {boolean} revs Include revision history of the document.
+ * - {boolean} revs_info Include revisions.
+ * - {boolean} conflicts Include conflicts.
+ * @param  {function} callback (optional) The callback(err,respons)
+ * @param  {function} error (optional) The callback on error, if this
+ *     callback is given in parameter, "callback" is changed as "success",
+ *     called on success.
+ */
+Object.defineProperty(that, "putAttachment", {
+  configurable: false,
+  enumerable: false,
+  writable: false,
+  value: function (doc, options, success, error) {
+    var param, k, doc_with_underscores = {};
+    param = priv.parametersToObject(
+      [options, success, error],
+      {max_retry: 0}
+    );
+    for (k in doc) {
+      if (doc.hasOwnProperty(k) && k.match('[^_].*')) {
+        doc_with_underscores["_" + k] = doc[k];
+      }
+    }
+
+    priv.addJob(putAttachmentCommand, {
+      doc: doc_with_underscores,
+      options: param.options,
+      callbacks: {success: param.success, error: param.error}
+    });
+  }
+});
+  return that;
+};                              // End Class jio
+/*jslint indent: 2, maxlen: 80, sloppy: true */
+/*global jio: true, invalidStorageType: true */
+var storage_type_object = { // -> 'key':constructorFunction
+  'base': function () {} // overriden by jio
+};
+var jioNamespace = (function (spec) {
+  var that = {};
+  spec = spec || {};
+  // Attributes //
+
+  // Methods //
+
+  /**
+   * Creates a new jio instance.
+   * @method newJio
+   * @param  {object} spec The storage description
+   * @return {object} The new Jio instance.
+   */
+  Object.defineProperty(that, "newJio", {
+    configurable: false,
+    enumerable: false,
+    writable: false,
+    value: function (spec) {
+      var storage = spec,
+        instance = null;
+      if (typeof storage === 'string') {
+        storage = JSON.parse(storage);
+      } else {
+        storage = JSON.stringify(storage);
+        if (storage !== undefined) {
+          storage = JSON.parse(storage);
+        }
+      }
+      storage = storage || {
+        type: 'base'
+      };
+      instance = jio(storage);
+      instance.start();
+      return instance;
+    }
+  });
+
+  /**
+   * Add a storage type to jio.
+   * @method addStorageType
+   * @param  {string} type The storage type
+   * @param  {function} constructor The associated constructor
+   */
+  Object.defineProperty(that, "addStorageType", {
+    configurable: false,
+    enumerable: false,
+    writable: false,
+    value: function (type, constructor) {
+      constructor = constructor || function () {
+        return null;
+      };
+      if (storage_type_object[type]) {
+        throw invalidStorageType({
+          type: type,
+          message: 'Already known.'
+        });
+      }
+      storage_type_object[type] = constructor;
+    }
+  });
+
+  return that;
+}());
+
+Object.defineProperty(scope, "jIO", {
+  configurable: false,
+  enumerable: false,
+  writable: false,
+  value: jioNamespace
+});
+}(window, hex_md5));