Index: Dev/trunk/src/.jshintrc-generated
===================================================================
--- Dev/trunk/src/.jshintrc-generated	(revision 486)
+++ Dev/trunk/src/.jshintrc-generated	(revision 487)
@@ -17,4 +17,5 @@
     "loopfunc": true,
     "shadow": true,
+    "eqnull": true,
 
     "devel": true,
Index: Dev/trunk/src/client/qed-client/app/Router.js
===================================================================
--- Dev/trunk/src/client/qed-client/app/Router.js	(revision 486)
+++ Dev/trunk/src/client/qed-client/app/Router.js	(revision 487)
@@ -78,5 +78,5 @@
                         route.callback(params);
                     } catch(err) {
-                        console.error("Page change failed with",err.toString());
+                        console.error("Page change failed with",err,err.toString());
                     }
                     return;
Index: Dev/trunk/src/client/qed-client/model/classes/Question.js
===================================================================
--- Dev/trunk/src/client/qed-client/model/classes/Question.js	(revision 486)
+++ 	(revision )
@@ -1,20 +1,0 @@
-define(function(){
-    return {
-        create: function(){
-            return { type:'Question' };
-        },
-        DisplayTitle: {
-            get: function(q) {
-                return q.title || '';
-            }
-        },
-        Content: {
-            get: function(q) {
-                return q.content || [];
-            },
-            set: function(q,content) {
-                q.content = content;
-            }
-        }
-    };
-});
Index: Dev/trunk/src/client/qed-client/model/classes/Response.js
===================================================================
--- Dev/trunk/src/client/qed-client/model/classes/Response.js	(revision 486)
+++ 	(revision )
@@ -1,17 +1,0 @@
-define([],function(){
-    var SurveyRun = {
-        create: function(){
-            return { type:'Response' };
-        },
-        SurveyRun: {
-            get: function(r) {
-                return r.surveyRunId || null;
-            },
-            set: function(r,sr) {
-                r.surveyRunId = sr;
-                return r;
-            }
-        }
-    };
-    return SurveyRun;
-});
Index: Dev/trunk/src/client/qed-client/model/classes/SessionTemplate.js
===================================================================
--- Dev/trunk/src/client/qed-client/model/classes/SessionTemplate.js	(revision 486)
+++ 	(revision )
@@ -1,12 +1,0 @@
-define(function(){
-    return {
-        create: function(){
-            return { type:'SessionTemplate' };
-        },
-        DisplayTitle: {
-            get: function(st) {
-                return st.title || '';
-            }
-        }
-    };
-});
Index: Dev/trunk/src/client/qed-client/model/classes/Survey.js
===================================================================
--- Dev/trunk/src/client/qed-client/model/classes/Survey.js	(revision 486)
+++ 	(revision )
@@ -1,20 +1,0 @@
-define(function(){
-    return {
-        create: function(){
-            return { type:'Survey' };
-        },
-        DisplayTitle: {
-            get: function(s) {
-                return s.title || '';
-            }
-        },
-        Questions: {
-            get: function(s) {
-                return s.questions || [];
-            },
-            set: function(s,questions) {
-                s.questions = questions;
-            }
-        }
-    };
-});
Index: Dev/trunk/src/client/qed-client/model/classes/SurveyRun.js
===================================================================
--- Dev/trunk/src/client/qed-client/model/classes/SurveyRun.js	(revision 486)
+++ 	(revision )
@@ -1,57 +1,0 @@
-define(['dojo/_base/lang','dojo/date/locale','dojo/date/stamp'],function(lang,locale,stamp){
-    var SurveyRun = {
-        create: function(){
-            return { type:'SurveyRun' };
-        },
-        StartDate: {
-            get: function(sr) {
-                var d;
-                if ( sr.startDate ) {
-                    d = lang.isString(sr.startDate) ? stamp.fromISOString(sr.startDate) : sr.startDate;
-                }
-                return d;
-            },
-            set: function(sr,d) {
-                if ( d ) {
-                    sr.startDate = lang.isString(d) ? stamp.toISOString(d) : d;
-                }
-            }
-        },
-        EndDate: {
-            get: function(sr) {
-                var d;
-                if ( sr.endDate ) {
-                    d = lang.isString(sr.endDate) ? stamp.fromISOString(sr.endDate) : sr.endDate;
-                }
-                return d;
-            },
-            set: function(sr,d) {
-                if ( d ) {
-                    sr.endDate = lang.isString(d) ? stamp.toISOString(d) : d;
-                }
-            }
-        },
-        DisplayTitle: {
-            get: function(sr) {
-                var t = "Run of '"+sr.survey.title+"'";
-                if ( sr.startDate ) {
-                    t += " from "+locale.format(SurveyRun.StartDate.get(sr));
-                }
-                if ( sr.endDate ) {
-                    t += " until "+locale.format(SurveyRun.EndDate.get(sr));
-                }
-                return t;
-            }
-        },
-        Survey: {
-            get: function(sr) {
-                return sr.survey || null;
-            },
-            set: function(sr,s) {
-                sr.survey = s;
-                return sr;
-            }
-        }
-    };
-    return SurveyRun;
-});
Index: Dev/trunk/src/client/qed-client/model/classes/_Class.js
===================================================================
--- Dev/trunk/src/client/qed-client/model/classes/_Class.js	(revision 487)
+++ Dev/trunk/src/client/qed-client/model/classes/_Class.js	(revision 487)
@@ -0,0 +1,51 @@
+define([
+    "./_View",
+    "dojo/_base/declare",
+    "dojo/_base/lang"
+], function(_View, declare, lang) {
+
+    var _Class = declare([_View],{
+        _type: null,
+        constructor: function() {
+            if ( this._type === null ) {
+                throw new Error("You must specify a concrete type.");
+            }
+        },
+        create: function() {
+            var obj = {
+                type: this._type
+            };
+            console.warn("You may want to override create for concrete types.");
+            return obj;
+        },
+        _doSerialize: function(obj) {
+            obj = lang.clone(obj);
+            return this._serialize(obj) || obj;
+        },
+        _serialize: function(obj) {
+            console.warn("You may want to override _serialize for concrete types.");
+        },
+        load: function(id) {
+            return this._store.get(id).then(lang.hitch(this,'_doDeserialize'));
+        },
+        save: function(obj) {
+            return this._store.put(this._doSerialize(obj));
+        },
+        remove: function(objOrId,rev) {
+            var id;
+            if ( typeof objOrId === "string" ) {
+                id = objOrId;
+            } else {
+                id = this.getId(objOrId);
+                rev = this.getRev(objOrId);
+            }
+            return this._store.remove(id,{headers:{'If-Match':'"'+rev+'"'}});
+        },
+        getRev: function(obj) {
+            return obj._rev;
+        }
+    });
+
+    return _Class;
+
+});
Index: Dev/trunk/src/client/qed-client/model/classes/_View.js
===================================================================
--- Dev/trunk/src/client/qed-client/model/classes/_View.js	(revision 487)
+++ Dev/trunk/src/client/qed-client/model/classes/_View.js	(revision 487)
@@ -0,0 +1,37 @@
+define([
+    "../../store/JsonRest",
+    "dojo/_base/declare",
+    "dojo/_base/lang",
+    "dojo/store/util/QueryResults"
+], function(JsonRest, declare, lang, queryResults) {
+
+    var _View = declare([],{
+        _collection: null,
+        _store: null,
+        constructor: function() {
+            if ( this._collection === null ) {
+                throw new Error("You must specify a concrete collection.");
+            }
+            this._store = new JsonRest({
+                target: '/api/'+this._collection+'/',
+                idProperty: '_id'
+            });
+        },
+        _doDeserialize: function(obj) {
+            obj = lang.clone(obj);
+            return this._deserialize(obj) || obj;
+        },
+        _deserialize: function(obj) {
+            console.warn("You may want to override _deserialize for concrete types.");
+        },
+        query: function(query) {
+            return queryResults(this._store.query(query).map(lang.hitch(this,'_doDeserialize')));
+        },
+        getId: function(obj) {
+            return obj._id;
+        }
+    });
+
+    return _View;
+
+});
Index: Dev/trunk/src/client/qed-client/model/classes/categories.js
===================================================================
--- Dev/trunk/src/client/qed-client/model/classes/categories.js	(revision 487)
+++ Dev/trunk/src/client/qed-client/model/classes/categories.js	(revision 487)
@@ -0,0 +1,16 @@
+define([
+    "./_View",
+    "dojo/_base/declare"
+], function(_View, declare) {
+
+    var Categories = declare([_View],{
+        _collection: 'categories',
+        _deserialize: function() {},
+        getId: function(obj) {
+            return obj;
+        }
+    });
+
+    return new Categories();
+
+});
Index: Dev/trunk/src/client/qed-client/model/classes/questions.js
===================================================================
--- Dev/trunk/src/client/qed-client/model/classes/questions.js	(revision 487)
+++ Dev/trunk/src/client/qed-client/model/classes/questions.js	(revision 487)
@@ -0,0 +1,34 @@
+define([
+    "./_Class",
+    "dojo/_base/declare",
+    "dojo/date/stamp"
+], function(_Class, declare, stamp) {
+
+    var Questions = declare([_Class],{
+        _collection: 'questions',
+        _type: 'Question',
+        create: function() {
+            var obj = {
+                type: this._type,
+                categories: [],
+                code: "",
+                content: [],
+                title: ""
+            };
+            return obj;
+        },
+        _deserialize: function(obj) {
+            if (obj.publicationDate) {
+                obj.publicationDate = stamp.fromISOString(obj.publicationDate);
+            }
+        },
+        _serialize: function(obj) {
+            if (obj.publicationDate) {
+                obj.publicationDate = stamp.toISOString(obj.publicationDate);
+            }
+        }
+    });
+
+    return new Questions();
+
+});
Index: Dev/trunk/src/client/qed-client/model/classes/responses.js
===================================================================
--- Dev/trunk/src/client/qed-client/model/classes/responses.js	(revision 487)
+++ Dev/trunk/src/client/qed-client/model/classes/responses.js	(revision 487)
@@ -0,0 +1,93 @@
+define([
+    "./_Class",
+    "./surveyRuns",
+    "dojo/Deferred",
+    "dojo/_base/declare",
+    "dojo/_base/json",
+    "dojo/_base/lang",
+    "dojo/_base/xhr",
+    "dojo/date/stamp"
+], function(_Class, surveyRuns, Deferred, declare, json, lang, xhr, stamp) {
+
+    var Responses = declare([_Class],{
+        _collection: 'responses',
+        _type: 'Response',
+        create: function() {
+            var obj = {
+                type: this._type,
+                answers: {},
+                surveyRunId: null
+            };
+            return obj;
+        },
+        _deserialize: function(obj) {
+            if (obj._surveyRun) {
+                obj._surveyRun = surveyRuns._doDeserialize(obj._surveyRun);
+            }
+            if (obj.publicationDate) {
+                obj.publicationDate = stamp.fromISOString(obj.publicationDate);
+            }
+        },
+        _serialize: function(obj) {
+            if (obj._surveyRun) {
+                obj._surveyRun = surveyRuns._doSerialize(obj._surveyRun);
+            }
+            if (obj.publicationDate) {
+                obj.publicationDate = stamp.toISOString(obj.publicationDate);
+            }
+        },
+        getWithSecret: function(id,secret) {
+            var query = xhr.objectToQuery({secret:secret});
+            return xhr('GET',{
+                url: '/api/open/responses/' + id + '?' + query,
+                handleAs: 'json',
+                contentType: false
+            }).then(lang.hitch(this,'_doDeserialize'),function(err){
+                return new Deferred().reject(json.fromJson(err.responseText));
+            });
+        },
+        postWithSecret: function(response,secret) {
+            var query = xhr.objectToQuery({secret:secret});
+            var body = json.toJson(this._doSerialize(response));
+            return xhr('POST',{
+                url: '/api/open/responses?' + query,
+                handleAs: 'json',
+                contentType: 'application/json',
+                rawBody: body
+            }).then(lang.hitch(this,'_doDeserialize'),function(err){
+                return new Deferred().reject(json.fromJson(err.responseText));
+            });
+        },
+        putWithSecret: function(response,secret) {
+            var query = xhr.objectToQuery({secret:secret});
+            var body = json.toJson(this._doSerialize(response));
+            return xhr('PUT',{
+                url: '/api/open/responses/' + this.getId(response) + '?' + query,
+                handleAs: 'json',
+                contentType: 'application/json',
+                rawBody: body
+            }).then(lang.hitch(this,'_doDeserialize'),function(err){
+                return new Deferred().reject(json.fromJson(err.responseText));
+            });
+        },
+        removeWithSecret: function(response,secret) {
+            var query = xhr.objectToQuery({secret:secret});
+            var rev = this.getRev(response);
+            var body = json.toJson(this._doSerialize(response));
+            var headers = {};
+            if ( rev ) {
+                headers['If-Match'] = '"'+rev+'"';
+            }
+            return xhr('DELETE',{
+                url: '/api/open/responses/' + this.getId(response) + '?' + query,
+                headers: headers,
+                handleAs: 'json',
+                contentType: 'application/json',
+                rawBody: body
+            });
+        }
+    });
+
+    return new Responses();
+
+});
Index: Dev/trunk/src/client/qed-client/model/classes/surveyRuns.js
===================================================================
--- Dev/trunk/src/client/qed-client/model/classes/surveyRuns.js	(revision 487)
+++ Dev/trunk/src/client/qed-client/model/classes/surveyRuns.js	(revision 487)
@@ -0,0 +1,47 @@
+define([
+    "./_Class",
+    "./surveys",
+    "dojo/_base/declare",
+    "dojo/date/stamp"
+], function(_Class, surveys, declare, stamp) {
+
+    var SurveyRuns = declare([_Class],{
+        _collection: 'surveyRuns',
+        _type: 'SurveyRun',
+        create: function() {
+            var obj = {
+                type: this._type,
+                description: "",
+                mode: "open",
+                survey: null,
+                title: ""
+            };
+            return obj;
+        },
+        _deserialize: function(obj) {
+            if (obj.endDate) {
+                obj.endDate = stamp.fromISOString(obj.endDate);
+            }
+            if (obj.startDate) {
+                obj.startDate = stamp.fromISOString(obj.startDate);
+            }
+            if (obj.survey) {
+                obj.survey = surveys._doDeserialize(obj.survey);
+            }
+        },
+        _serialize: function(obj) {
+            if (obj.endDate) {
+                obj.endDate = stamp.toISOString(obj.endDate);
+            }
+            if (obj.startDate) {
+                obj.startDate = stamp.toISOString(obj.startDate);
+            }
+            if (obj.survey) {
+                obj.survey = surveys._doSerialize(obj.survey);
+            }
+        }
+    });
+
+    return new SurveyRuns();
+
+});
Index: Dev/trunk/src/client/qed-client/model/classes/surveys.js
===================================================================
--- Dev/trunk/src/client/qed-client/model/classes/surveys.js	(revision 487)
+++ Dev/trunk/src/client/qed-client/model/classes/surveys.js	(revision 487)
@@ -0,0 +1,33 @@
+define([
+    "./_Class",
+    "dojo/_base/declare",
+    "dojo/date/stamp",
+    "dojo/store/JsonRest"
+], function(_Class, declare, stamp, JsonRest) {
+
+    var Surveys = declare([_Class],{
+        _collection: 'surveys',
+        _type: 'Survey',
+        create: function() {
+            var obj = {
+                type: this._type,
+                questions: [],
+                title: ""
+            };
+            return obj;
+        },
+        _deserialize: function(obj) {
+            if (obj.publicationDate) {
+                obj.publicationDate = stamp.fromISOString(obj.publicationDate);
+            }
+        },
+        _serialize: function(obj) {
+            if (obj.publicationDate) {
+                obj.publicationDate = stamp.toISOString(obj.publicationDate);
+            }
+        }
+    });
+
+    return new Surveys();
+
+});
Index: Dev/trunk/src/client/qed-client/model/classes/topics.js
===================================================================
--- Dev/trunk/src/client/qed-client/model/classes/topics.js	(revision 487)
+++ Dev/trunk/src/client/qed-client/model/classes/topics.js	(revision 487)
@@ -0,0 +1,16 @@
+define([
+    "./_View",
+    "dojo/_base/declare"
+], function(_View, declare) {
+
+    var Topics = declare([_View],{
+        _collection: 'topics',
+        _deserialize: function() {},
+        getId: function(obj) {
+            return obj;
+        }
+    });
+
+    return new Topics();
+
+});
Index: Dev/trunk/src/client/qed-client/model/schema.js
===================================================================
--- Dev/trunk/src/client/qed-client/model/schema.js	(revision 486)
+++ 	(revision )
@@ -1,157 +1,0 @@
-define(['dojo/_base/lang'],function(lang){
-
-    /** Note on dojox.json.schema
-     * This implementation is not according to the version 03 of the json-schema
-     * draft. Differences:
-     * - doesn't support the 'required' property, but uses 'optional' (which is
-     *   false by default).
-     */
-
-    ////////////////////////////////////////
-    // Base schema types
-    //
-
-    var dateRe = "\\d{4}-\\d{2}-\\d{2}"; // YYYY-MM-DD
-    var timeRe = "\\d{2}:\\d{2}:\\d{2}"; // hh:mm:ss 
-    var datetimeRe = dateRe+"T"+timeRe+"Z"; // YYYY-MM-DDThh:mm:ssZ
-
-    function mixin(obj1,obj2) {
-        return lang.mixin({},obj1||{},obj2||{});
-    }
-    function mkType(base) {
-        return function(props) {
-            return mixin(base,props);
-        };
-    }
-
-    var nestring = mkType({ type:'string', minLength:1 });
-    var string = mkType({ type:'string' });
-    var integer = mkType({ type: 'integer' });
-    var bool = mkType({ type: 'boolean' });
-    var pattern = function(p){
-        return mixin({
-            type: 'string',
-            pattern: p
-        });
-    };
-    var date = mkType(pattern(dateRe));
-    var time = mkType(pattern(timeRe));
-    var datetime = mkType(pattern(datetimeRe));
-    var array = function(items,props) {
-        return mixin({
-            type: 'array',
-            items: items
-        },props);
-    };
-    var object = function(properties,props) {
-        return mixin({
-            type: 'object',
-            properties: properties,
-            additionalProperties: false
-        },props);
-    };
-    var optional = mkType({ optional: true });
-
-    ////////////////////////////////////////
-    // Domain specific
-    //
-
-    var typedobject = function(type,properties,props) {
-        return object(mixin(properties,{
-            type: pattern(type)
-        }),props);
-    };
-
-    var dbobject = function(type,properties,props) {
-        return typedobject(type,mixin(properties,{
-            _id: nestring(optional()),
-            _rev: nestring(optional())
-        }),props);
-    };
-
-    ////////////////////////////////////////
-    // The QED schema
-    //
-
-    var Respondent = dbobject('Respondent',{
-        secret: nestring()
-    });
-
-    var Question = dbobject('Question',{
-        code:nestring(),
-        title: nestring(),
-        description: string(optional()),
-        topic: string(optional()),
-        categories: array(nestring(),optional()),
-        content: array([
-            typedobject('Header',{
-                content:string(optional())
-            }),
-            typedobject('Text',{
-                content:string(optional())
-            }),
-            typedobject('Divider'),
-            typedobject('StringInput'),
-            typedobject('TextInput',{
-                maxLength:integer()
-            }),
-            typedobject('IntegerInput',{
-                min:integer(),
-                max:integer(),
-                step:integer()
-            }),
-            typedobject('MultipleChoiceInput',{
-                multiple:bool(),
-                items:array(nestring())
-            })
-        ],optional()),
-        publicationDate:datetime(optional())
-    });
-
-    var Survey = dbobject('Survey',{
-        title: nestring(),
-        description: string(optional()),
-        questions: array(Question,optional()),
-        publicationDate:datetime(optional())
-    });
-
-    var SurveyRun = dbobject('SurveyRun',{
-        description: string(optional()),
-        survey: Survey,
-        publicationDate:datetime(optional()),
-        startDate:datetime(optional()),
-        endDate:datetime(optional()),
-        mode:{type:'string','enum':['open','closed'],optional:true},
-        metadata:string(optional())
-    });
-
-    var Response = dbobject('Response',{
-        surveyRunId: nestring(),
-        answers:{type:'object',optional:true},
-        publicationDate:datetime(optional())
-    });
-
-    var Session = dbobject('Session',{
-        title: nestring(),
-        description: string(optional()),
-        publicationDate: datetime(optional())
-    });
-
-    var SessionTemplate = dbobject('SessionTemplate',{
-        title: nestring(),
-        description: string(optional()),
-        publicationDate: datetime(optional()),
-        plannedDate: datetime(optional())
-    });
-
-    var schema = {type:[
-        Question,
-        Survey,
-        SurveyRun,
-        Response,
-        Session,
-        SessionTemplate
-    ]};
-
-    return schema;
-});
Index: Dev/trunk/src/client/qed-client/model/widgets/QuestionEditorToolkit.js
===================================================================
--- Dev/trunk/src/client/qed-client/model/widgets/QuestionEditorToolkit.js	(revision 486)
+++ Dev/trunk/src/client/qed-client/model/widgets/QuestionEditorToolkit.js	(revision 487)
@@ -1,4 +1,5 @@
 define([
-    "../../store",
+    "../classes/categories",
+    "../classes/topics",
     "./CategoryListView",
     "dijit/_Container",
@@ -15,5 +16,5 @@
     "require",
     "dojo/text!./templates/QuestionEditorToolkit.html"
-], function(store, CategoryListView, _Container, _TemplatedMixin, _WidgetBase, _WidgetsInTemplateMixin, Button, ComboBox, declare, lang, Source, domConstruct, Memory, require, template) {
+], function(categories, topics, CategoryListView, _Container, _TemplatedMixin, _WidgetBase, _WidgetsInTemplateMixin, Button, ComboBox, declare, lang, Source, domConstruct, Memory, require, template) {
     return declare([_WidgetBase, _TemplatedMixin, _WidgetsInTemplateMixin, _Container], {
 
@@ -107,12 +108,10 @@
             this.inherited(arguments);
 
-            store.query("_design/questions/_view/all", {reduce:true, group:false, group_level:1})
-            .forPairs(lang.hitch(this, function(value, key) {
-                this._categoryStore.put({ id: key[0] });
+            categories.query().forEach(lang.hitch(this,function(cat){
+                this._categoryStore.put({ id: cat });
             }));
 
-            store.query("_design/questions/_view/all_topics", {reduce:true, group:true})
-            .forPairs(lang.hitch(this, function(value, key) {
-                this._topicStore.put({ id: key });
+            topics.query().forEach(lang.hitch(this,function(topic){
+                this._categoryStore.put({ id: topic });
             }));
         },
Index: Dev/trunk/src/client/qed-client/model/widgets/SurveyRenderWidget.js
===================================================================
--- Dev/trunk/src/client/qed-client/model/widgets/SurveyRenderWidget.js	(revision 486)
+++ Dev/trunk/src/client/qed-client/model/widgets/SurveyRenderWidget.js	(revision 487)
@@ -1,5 +1,4 @@
 define([
     "../../widgets/_ComplexValueWidget",
-    "../classes/Survey",
     "./questions/Factory",
     "dojo/_base/array",
@@ -7,5 +6,5 @@
     "dojo/dom-construct",
     "dojo/text!./templates/SurveyRenderWidget.html"
-], function(_ComplexValueWidget, Survey, QuestionWidgetFactory, array, declare, domConstruct, template) {
+], function(_ComplexValueWidget, QuestionWidgetFactory, array, declare, domConstruct, template) {
     return declare([_ComplexValueWidget],{
         templateString: template,
@@ -22,5 +21,5 @@
             this.survey = survey;
             var f = new QuestionWidgetFactory();
-            array.forEach(Survey.Questions.get(this.survey),function(question,question_index){
+            array.forEach(this.survey.questions,function(question,question_index){
                 array.forEach(question.content || [], function(item,item_index){
                     // The dot causes values to be grouped in an object!
Index: Dev/trunk/src/client/qed-client/model/widgets/SurveySummary.js
===================================================================
--- Dev/trunk/src/client/qed-client/model/widgets/SurveySummary.js	(revision 486)
+++ Dev/trunk/src/client/qed-client/model/widgets/SurveySummary.js	(revision 487)
@@ -1,5 +1,3 @@
 define([
-    "../../store",
-    "../classes/Survey",
     "dijit/_TemplatedMixin",
     "dijit/_WidgetBase",
@@ -7,5 +5,5 @@
     "dojo/dom-attr",
     "dojo/text!./templates/SurveySummary.html"
-], function(store, Survey, _TemplatedMixin, _WidgetBase, declare, domAttr, template) {
+], function(_TemplatedMixin, _WidgetBase, declare, domAttr, template) {
     return declare([_WidgetBase,_TemplatedMixin],{
         templateString: template,
@@ -18,9 +16,8 @@
         },
         _setValueAttr: function(survey) {
-            this.titleNode.innerHTML = Survey.DisplayTitle.get(survey);
-            var id = store.getIdentity(survey);
-            domAttr.set(this.titleNode, "href", id && ("#!/survey/"+id));
+            this.titleNode.innerHTML = survey.title || "";
+            domAttr.set(this.titleNode, "href", survey._id && ("#!/survey/"+survey._id));
             this.descriptionNode.innerHTML = survey.description;
-            this.questionsNode.innerHTML = (survey.questions || []).length;
+            this.questionsNode.innerHTML = survey.questions.length;
         }
     });
Index: Dev/trunk/src/client/qed-client/model/widgets/TabbedQuestionBrowser.js
===================================================================
--- Dev/trunk/src/client/qed-client/model/widgets/TabbedQuestionBrowser.js	(revision 486)
+++ Dev/trunk/src/client/qed-client/model/widgets/TabbedQuestionBrowser.js	(revision 487)
@@ -1,12 +1,14 @@
 define([
-    'dojo/_base/declare',
-    'dojo/_base/lang',
-    'dojo/_base/window',
-    'dijit/layout/ContentPane',
-    'dijit/layout/TabContainer',
-    'dojox/widget/Standby',
-    '../../store',
-    '../../widgets/Selector'
-],function(declare,lang,win,ContentPane,TabContainer,Standby,store,Selector){
+    "../../widgets/Selector",
+    "../classes/categories",
+    "../classes/questions",
+    "../classes/topics",
+    "dijit/layout/ContentPane",
+    "dijit/layout/TabContainer",
+    "dojo/_base/declare",
+    "dojo/_base/lang",
+    "dojo/_base/window",
+    "dojox/widget/Standby"
+], function(Selector, categories, questions, topics, ContentPane, TabContainer, declare, lang, win, Standby) {
     return declare([TabContainer],{
         tabPosition: 'left-h',
@@ -38,7 +40,7 @@
                 this._fillCategoryTab(newTab.__category);
             }));
-            store.query(this._query, {reduce:true,group:true,group_level:1})
-            .forPairs(lang.hitch(this,function(value,key){
-                this._createCategoryTab(key[0],value);
+            categories.query()
+            .forEach(lang.hitch(this,function(cat){
+                this._createCategoryTab(cat.name,cat.count);
             }));
         },
@@ -61,7 +63,7 @@
                 this._busy();
                 categoryMap._filled = true;
-                store.query(this._query, {reduce:true,group:true,group_level:2,startkey:[category],endkey:[category,{}]})
-                .forPairs(lang.hitch(this,function(value,key){
-                    this._createTopicSelector(key[1],category,value);
+                topics.query({category:category})
+                .forEach(lang.hitch(this,function(topic){
+                    this._createTopicSelector(topic.name,category,topic.count);
                 })).then(lang.hitch(this,function(){
                     this._done();
@@ -101,9 +103,6 @@
                 topicMap._filled = true;
                 this._busy();
-                store.query(this._query, {
-                    reduce:false,
-                    include_docs:true,
-                    key:[category,topic]
-                }).forEach(lang.hitch(this,function(value){
+                questions.query({category:category,topic:topic})
+                .forEach(lang.hitch(this,function(value){
                     topicMap._widget.addItem(value);
                 })).then(lang.hitch(this,function(){
Index: Dev/trunk/src/client/qed-client/pages/previewSurvey.js
===================================================================
--- Dev/trunk/src/client/qed-client/pages/previewSurvey.js	(revision 486)
+++ Dev/trunk/src/client/qed-client/pages/previewSurvey.js	(revision 487)
@@ -1,5 +1,5 @@
 define([
     "../app/Page",
-    "../store",
+    "../model/classes/surveys",
     "dojo/_base/array",
     "dojo/_base/declare",
@@ -8,5 +8,5 @@
     "require",
     "dojo/text!./templates/previewSurvey.html"
-], function(Page, store, array, declare, lang, when, require, template) {
+], function(Page, surveys, array, declare, lang, when, require, template) {
     return declare([Page],{
         contextRequire: require,
@@ -16,7 +16,7 @@
             this.inherited(arguments);
             if ( this.surveyId ) {
-                when(store.get(this.surveyId))
+                when(surveys.load(this.surveyId))
                 .then(lang.hitch(this,function(survey){
-                    this.titleNode.innerHTML = survey.title;
+                    this.titleNode.innerHTML = survey.title || "";
                     this.surveyWidget.set('survey',survey);
                 }));
Index: Dev/trunk/src/client/qed-client/pages/question.js
===================================================================
--- Dev/trunk/src/client/qed-client/pages/question.js	(revision 486)
+++ Dev/trunk/src/client/qed-client/pages/question.js	(revision 487)
@@ -3,8 +3,7 @@
     "../app/Page",
     "../app/Router",
-    "../model/classes/Question",
+    "../model/classes/questions",
     "../model/widgets/QuestionEditorPreview",
     "../model/widgets/QuestionEditorToolkit",
-    "../store",
     "../widgets/_ComplexValueMixin",
     "dojo/_base/declare",
@@ -13,5 +12,5 @@
     "dojo/when",
     "dojo/text!./templates/question.html"
-], function(Content, Page, Router, Question, QuestionEditorPreview, QuestionEditorToolkit, store, _ComplexValueMixin, declare, event, lang, when, template) {
+], function(Content, Page, Router, questions, QuestionEditorPreview, QuestionEditorToolkit, _ComplexValueMixin, declare, event, lang, when, template) {
     return declare([Page,_ComplexValueMixin], {
         templateString: template,
@@ -43,7 +42,7 @@
             }
             if (this.questionId === "new") {
-                this.set('value', Question.create());
+                this.set('value', questions.create());
             } else {
-                when(store.get(this.questionId))
+                when(questions.load(this.questionId))
                 .then(lang.hitch(this, function(value) {
                     this.set('value', value);
@@ -54,5 +53,5 @@
             this.value = value;
             this.inherited(arguments);
-            this.titleNode.innerHTML = Question.DisplayTitle.get(value);
+            this.titleNode.innerHTML = value.title || "";
         },
         _getValueAttr: function() {
@@ -63,6 +62,5 @@
         _onSave: function(evt) {
             if ( this.validate() ) {
-                var value = this.get('value');
-                store.put(value)
+                questions.save(this.get('value'))
                 .then(function() {
                     Router.go('/questions');
Index: Dev/trunk/src/client/qed-client/pages/questions.js
===================================================================
--- Dev/trunk/src/client/qed-client/pages/questions.js	(revision 486)
+++ Dev/trunk/src/client/qed-client/pages/questions.js	(revision 487)
@@ -1,14 +1,14 @@
 define([
-    'dojo/_base/declare',
-    'dojo/_base/Deferred',
-    'dojo/_base/event',
-    'dojo/_base/lang',
-    '../store',
-    '../app/Content',
-    '../app/Router',
-    '../app/Page',
-    '../model/widgets/TabbedQuestionBrowser',
-    'dojo/text!./templates/questions.html'
-],function(declare,Deferred,event,lang,store,Content,Router,Page,TabbedQuestionBrowser,template) {
+    "../app/Content",
+    "../app/Page",
+    "../app/Router",
+    "../model/classes/questions",
+    "../model/widgets/TabbedQuestionBrowser",
+    "dojo/_base/Deferred",
+    "dojo/_base/declare",
+    "dojo/_base/event",
+    "dojo/_base/lang",
+    "dojo/text!./templates/questions.html"
+], function(Content, Page, Router, questions, TabbedQuestionBrowser, Deferred, declare, event, lang, template) {
     return declare([Page],{
         templateString: template,
@@ -44,5 +44,5 @@
         },
         onDeleteQuestion: function(question) {
-            store.remove(store.getIdentity(question),store.getRevision(question))
+            questions.remove(question)
             .then(function(){
                 Content.notify("Question deleted.");
@@ -55,6 +55,6 @@
         },
         onPublishQuestion: function(question) {
-            question.publicationDate = store.timestamp();
-            store.put(question)
+            question.publicationDate = new Date();
+            questions.save(question)
             .then(function(){
                 Content.notify("Question published.");
Index: Dev/trunk/src/client/qed-client/pages/response.js
===================================================================
--- Dev/trunk/src/client/qed-client/pages/response.js	(revision 486)
+++ Dev/trunk/src/client/qed-client/pages/response.js	(revision 487)
@@ -3,8 +3,5 @@
     "../app/Page",
     "../lib/async",
-    "../model/classes/Response",
-    "../model/classes/Survey",
-    "../model/classes/SurveyRun",
-    "../store",
+    "../model/classes/responses",
     "dojo/_base/declare",
     "dojo/_base/event",
@@ -16,5 +13,5 @@
     "require",
     "dojo/text!./templates/response.html"
-], function(Content, Page, async, Response, Survey, SurveyRun, store, declare, event, json, lang, all, request, when, require, template) {
+], function(Content, Page, async, responses, declare, event, json, lang, all, request, when, require, template) {
     return declare([Page],{
         contextRequire: require,
@@ -28,35 +25,17 @@
             this.inherited(arguments);
             this._disableSubmit();
-            var surveyRunId = this.surveyRunId;
-            var responseId = this.options && this.options.id;
-            if ( surveyRunId && responseId ) {
-                this._loadSurveyAndResponse(surveyRunId,responseId)
-                .then(lang.hitch(this, function() {
-                    if ( this.response.publicationDate ) {
-                        this._showInfo("<div>You already submitted your survey and cannot change it anymore. You can still view your answers here.</div>");
-                        this._disableSubmit();
-                    } else {
-                        this._enableSubmit();
-                    }
-                }), lang.hitch(this,function() {
-                    this._showInfo("<div>The url seems to be incorrect, no survey found.</div>");
-                }));
+            if ( !this.response ) {
+                this._showInfo("<div>The url seems to be incorrect, no response found.</div>");
             } else {
-                throw new Error("No valid uid or survey passed!");
+                this.titleNode.innerHTML = this.response._surveyRun.survey.title || "";
+                this.surveyWidget.set('survey', this.response._surveyRun.survey);
+                this.surveyWidget.set('value', this.response.answers || {});
+                if ( this.response.publicationDate ) {
+                    this._showInfo("<div>You already submitted your survey and cannot change it anymore. You can still view your answers here.</div>");
+                    this._disableSubmit();
+                } else {
+                    this._enableSubmit();
+                }
             }
-        },
-        _loadSurveyAndResponse: function(surveyRunId,responseId){
-            return all([request.get('/api/surveyRuns/'+surveyRunId,{handleAs:'json'}),
-                        request.get('/api/responses/'+responseId,{handleAs:'json'})])
-            .then(lang.hitch(this,function(surveyAndResponse){
-                var surveyRun = surveyAndResponse[0];
-                this.response = surveyAndResponse[1];
-                if ( this.response.surveyRunId !== surveyRunId ) {
-                    throw "Survey does not match the response...";
-                }
-                this.titleNode.innerHTML = Survey.DisplayTitle.get(surveyRun.survey);
-                this.surveyWidget.set('survey', surveyRun.survey);
-                this.surveyWidget.set('value', this.response.answers || {});
-            }));
         },
         _enableSubmit: function() {
@@ -78,10 +57,7 @@
             var answers = this.surveyWidget.get('value');
             this.response.answers = answers;
-            return request.put('/api/responses/'+store.getIdentity(this.response),{
-                handleAs:'json',
-                data:json.toJson(this.response),
-                headers:{"Content-Type": "application/json"}
-            }).then(lang.hitch(this,function(res){
-                this.response._rev = res.rev;
+            return responses.putWithSecret(this.response,this.response.secret)
+            .then(lang.hitch(this,function(response){
+                this.response = response;
                 Content.notify("Your response is saved.");
             }), function(err){
@@ -90,5 +66,5 @@
         },
         _onSubmit: function(e) {
-            this.response.publicationDate = store.timestamp();
+            this.response.publicationDate = new Date();
             this._getAnswersAndSaveResponse()
             .then(lang.hitch(this,function(){
@@ -110,10 +86,6 @@
             this._disableSubmit();
             this.surveyWidget.destroy();
-            request('/api/responses/'+store.getIdentity(this.response)+'?rev='+store.getRevision(this.response),{
-                method: 'DELETE',
-                handleAs:'json',
-                data:json.toJson(this.response),
-                headers:{"Content-Type": "application/json"}
-            }).then(lang.hitch(this,function(res){
+            responses.removeWithSecret(this.response,this.response.secret)
+            .then(lang.hitch(this,function(res){
                 this._showInfo("<div>Your response has been deleted, no answers have been saved.</div>");
                 Content.notify("Your response is deleted.");
Index: Dev/trunk/src/client/qed-client/pages/session.js
===================================================================
--- Dev/trunk/src/client/qed-client/pages/session.js	(revision 486)
+++ Dev/trunk/src/client/qed-client/pages/session.js	(revision 487)
@@ -1,4 +1,4 @@
 define([
-    'dojo/_base/array',
+    /*'dojo/_base/array',
     'dojo/_base/declare',
     'dojo/_base/event',
@@ -12,7 +12,7 @@
     '../model/classes/SessionTemplate',
     '../model/widgets/AccountListView',
-    'dojo/text!./templates/session.html'
+    'dojo/text!./templates/session.html'*/
 ],function(array,declare,event,lang,when,search,store,Page,Router,ThresholdFilteringSelect,SessionTemplate,AccountListView,template){
-    return declare([Page],{
+    /*return declare([Page],{
         templateString: template,
         session: null,
@@ -92,5 +92,5 @@
 
 
-    });
+    });*/
 });
 
Index: Dev/trunk/src/client/qed-client/pages/sessions.js
===================================================================
--- Dev/trunk/src/client/qed-client/pages/sessions.js	(revision 486)
+++ Dev/trunk/src/client/qed-client/pages/sessions.js	(revision 487)
@@ -1,4 +1,4 @@
 define([
-    'dojo/_base/declare',
+    /*'dojo/_base/declare',
     'dojo/_base/lang',
     'dojo/date/stamp',
@@ -7,7 +7,7 @@
     '../app/Page',
     '../widgets/ObjectBox',
-    'dojo/text!./templates/sessions.html'
+    'dojo/text!./templates/sessions.html'*/
 ],function(declare,lang,dateStamp,store,Router,Page,ObjectBox,template){
-    return declare([Page],{
+    /*return declare([Page],{
         templateString: template,
         templateActions: null,
@@ -18,8 +18,8 @@
             this.templateActions = {
                 "Edit": function(obj){
-                    Router.go('/session/'+store.getIdentity(obj));
+                    Router.go('/session/'+obj.get('id'));
                 },
                 "Delete": lang.hitch(this,function(obj){
-                    store.remove(store.getIdentity(obj),store.getRevision(obj))
+                    obj.remove()
                     .then(lang.hitch(this,function(){
                         this._refresh();
@@ -30,8 +30,8 @@
             this.sessionActions = {
                 "Facilitate": function(obj){
-                    Router.go('run',{uid:store.getIdentity(obj)});
+                    Router.go('run',{uid: obj.get('id')});
                 },
                 "Delete": lang.hitch(this,function(obj){
-                    store.remove(store.getIdentity(obj),store.getRevision(obj))
+                    obj.remove()
                     .then(lang.hitch(this,function(){
                         this._refresh();
@@ -48,4 +48,5 @@
         },
         _refreshByType: function(type,container,actions) {
+            // FIXME
             store.query("_design/default/_view/by_type",{key:type})
             .forEach(lang.hitch(this,function(obj){
@@ -61,4 +62,5 @@
         },
         _publishSession: function(sessionTemplate) {
+            // FIXME
             var session = lang.clone(sessionTemplate);
             delete session[store.idProperty];
@@ -73,4 +75,4 @@
             }));
         }
-    });
+    });*/
 });
Index: Dev/trunk/src/client/qed-client/pages/survey.js
===================================================================
--- Dev/trunk/src/client/qed-client/pages/survey.js	(revision 486)
+++ Dev/trunk/src/client/qed-client/pages/survey.js	(revision 487)
@@ -2,8 +2,7 @@
     "../app/Page",
     "../app/Router",
-    "../model/classes/Survey",
+    "../model/classes/surveys",
     "../model/widgets/QuestionListView",
     "../model/widgets/TabbedQuestionBrowser",
-    "../store",
     "dojo/_base/array",
     "dojo/_base/declare",
@@ -13,5 +12,5 @@
     "require",
     "dojo/text!./templates/survey.html"
-], function(Page, Router, Survey, QuestionListView, TabbedQuestionBrowser, store, array, declare, event, lang, when, require, template) {
+], function(Page, Router, surveys, QuestionListView, TabbedQuestionBrowser, array, declare, event, lang, when, require, template) {
     return declare([Page],{
         contextRequire: require,
@@ -63,12 +62,12 @@
         _loadSurvey: function() {
             if ( this.surveyId === "new" ) {
-                this.survey = Survey.create();
+                this.survey = surveys.create();
                 this.refresh();
             } else {
-                when(store.get(this.surveyId))
+                when(surveys.load(this.surveyId))
                 .then(lang.hitch(this,function(survey){
                     this.survey = survey;
                     this.questionList.set('value',
-                                          Survey.Questions.get(this.survey));
+                                          this.survey.questions);
                     this.refresh();
                 }));
@@ -79,5 +78,5 @@
         },
         refresh: function() {
-            this.titleNode.innerHTML = Survey.DisplayTitle.get(this.survey) || "(set title in properties)";
+            this.titleNode.innerHTML = this.survey.title || "(set title in properties)";
             this.propertiesDialog.set('value',this.survey);
         },
@@ -100,5 +99,5 @@
         _onSave: function(evt) {
             this.survey.questions = this.questionList.get('value');
-            store.put(this.survey)
+            surveys.save(this.survey)
             .then(function() { 
                 Router.go('/surveys');
@@ -111,5 +110,5 @@
         },
         _onShowPreview: function() {
-            Router.go('/previewSurvey/'+store.getIdentity(this.survey),{
+            Router.go('/previewSurvey/'+this.survey._id,{
                 preview: true
             });
Index: Dev/trunk/src/client/qed-client/pages/surveyRun.js
===================================================================
--- Dev/trunk/src/client/qed-client/pages/surveyRun.js	(revision 486)
+++ Dev/trunk/src/client/qed-client/pages/surveyRun.js	(revision 487)
@@ -4,7 +4,8 @@
     "../app/Router",
     "../lib/func",
-    "../model/classes/SurveyRun",
-    "../store",
+    "../model/classes/responses",
+    "../model/classes/surveyRuns",
     "../widgets/LineWithActionsWidget",
+    "dojo/_base/array",
     "dojo/_base/declare",
     "dojo/_base/event",
@@ -13,5 +14,5 @@
     "require",
     "dojo/text!./templates/surveyRun.html"
-], function(Content, Page, Router, func, SurveyRun, store, LineWithActionsWidget, declare, event, lang, when, require, template) {
+], function(Content, Page, Router, func, responses, surveyRuns, LineWithActionsWidget, array, declare, event, lang, when, require, template) {
     return declare([Page],{
         contextRequire: require,
@@ -24,5 +25,4 @@
             if ( this.surveyRunId ) {
                 this._loadSurveyRun();
-                this._loadResponses();
             } else {
                 throw "No valid uid or survey passed!";
@@ -30,41 +30,44 @@
         },
         _loadSurveyRun: function() {
-            when(store.get(this.surveyRunId))
+            when(surveyRuns.load(this.surveyRunId))
             .then(lang.hitch(this,function(surveyRun){
                 this.surveyRun = surveyRun;
                 this.refreshSurveyRun();
+                this._loadResponses();
             }));
         },
         refreshSurveyRun: function() {
-            this.titleNode.innerHTML = SurveyRun.DisplayTitle.get(this.surveyRun);
-            this.surveySummaryWidget.set('value',SurveyRun.Survey.get(this.surveyRun));
+            this.titleNode.innerHTML = this.surveyRun.title || "";
+            this.surveySummaryWidget.set('value',this.surveyRun.survey);
             this.surveyRunWidget.set('value',this.surveyRun);
             this._onPropChange();
         },
         _loadResponses: function() {
-            when(store.query("_design/responses/_view/by_surveyrun",{key:this.surveyRunId}))
-            .forEach(lang.hitch(this,function(response){
-                var actions = {
-                    view: {
-                        callback: function(){},
-                        properties: {
-                            title: "View response"
-                        }
-                    }
-                };
-                if ( !response.publicationDate ) {
-                    actions.remove = {
-                        callback: function(){},
-                        properties: {
-                            title: "Remove response"
+            responses.query({surveyRunId:surveyRuns.getId(this.surveyRun)})
+            .then(lang.hitch(this,function(allResponses){
+                array.forEach(allResponses, function(response){
+                    var actions = {
+                        view: {
+                            callback: function(){},
+                            properties: {
+                                title: "View response"
+                            }
                         }
                     };
-                }
-                var w = new LineWithActionsWidget({
-                    actions: actions
-                });
-                var responseId = store.getIdentity(response);
-                w.set('title',this._link(this._getResponseURL(this.surveyRunId,responseId),responseId));
-                w.placeAt(this.responsesNode);
+                    if ( !response.publicationDate ) {
+                        actions.remove = {
+                            callback: function(){},
+                            properties: {
+                                title: "Remove response"
+                            }
+                        };
+                    }
+                    var w = new LineWithActionsWidget({
+                        actions: actions
+                    });
+                    var rid = responses.getId(response);
+                    w.set('title',this._link(this._buildResponseURL(response),rid),rid);
+                    w.placeAt(this.responsesNode);
+                }, this);
             }));
         },
@@ -73,5 +76,5 @@
             if ( surveyRun.mode === "open" ) {
                 this.runURLNode.innerHTML =
-                    this._link(this._getGeneralURL(store.getIdentity(this.surveyRun)));
+                    this._link(this._buildGeneralURL(this.surveyRun));
             } else {
                 this.runURLNode.innerHTML =
@@ -79,9 +82,9 @@
             }
         },
-        _getGeneralURL: function(surveyRunId) {
-            return 'response.html#!/'+surveyRunId;
+        _buildGeneralURL: function(surveyRun) {
+            return 'response.html#!/surveyRuns/'+surveyRuns.getId(surveyRun)+'!secret='+surveyRun.secret;
         },
-        _getResponseURL: function(surveyRunId,responseId) {
-            return 'response.html#!/'+surveyRunId+'!id='+responseId;
+        _buildResponseURL: function(response) {
+            return 'response.html#!/responses/'+responses.getId(response)+'!secret='+response.secret;
         },
         _link: function(url,label) {
@@ -92,10 +95,5 @@
                 lang.mixin(this.surveyRun,this.surveyRunWidget.get('value'));
 
-                var SD = SurveyRun.StartDate;
-                var ED = SurveyRun.EndDate;
-                SD.set(this.surveyRun, SD.get(this.surveyRun));
-                ED.set(this.surveyRun, ED.get(this.surveyRun));
-
-                store.put(this.surveyRun)
+                surveyRuns.save(this.surveyRun)
                 .then(function() { 
                     Router.go('/surveys');
Index: Dev/trunk/src/client/qed-client/pages/surveys.js
===================================================================
--- Dev/trunk/src/client/qed-client/pages/surveys.js	(revision 486)
+++ Dev/trunk/src/client/qed-client/pages/surveys.js	(revision 487)
@@ -1,16 +1,15 @@
 define([
-    'dojo/_base/array',
-    'dojo/_base/declare',
-    'dojo/_base/lang',
-    'dojo/when',
-    '../store',
-    '../app/Content',
-    '../app/Page',
-    '../app/Router',
-    '../model/classes/Survey',
-    '../model/classes/SurveyRun',
-    '../widgets/LineWithActionsWidget',
-    'dojo/text!./templates/surveys.html'
-],function(array,declare,lang,when,store,Content,Page,Router,Survey,SurveyRun,LineWithActionsWidget,template){
+    "../app/Content",
+    "../app/Page",
+    "../app/Router",
+    "../model/classes/surveys",
+    "../model/classes/surveyRuns",
+    "../widgets/LineWithActionsWidget",
+    "dojo/_base/array",
+    "dojo/_base/declare",
+    "dojo/_base/lang",
+    "dojo/when",
+    "dojo/text!./templates/surveys.html"
+], function(Content, Page, Router, surveys, surveyRuns, LineWithActionsWidget, array, declare, lang, when, template) {
     return declare([Page],{
         templateString: template,
@@ -25,6 +24,7 @@
         _onPublishSurvey:function(survey){
             var self = this;
-            survey.publicationDate = store.timestamp();
-            store.put(survey).then(function(){
+            survey.publicationDate = new Date();
+            surveys.save(survey)
+            .then(function(){
                 self.refreshDrafts();
                 self.refreshPublished();
@@ -35,5 +35,5 @@
         _onDeleteSurvey:function(survey){
             var self = this;
-            store.remove(store.getIdentity(survey),store.getRevision(survey))
+            surveys.remove(survey)
             .then(function(){
                 self.refreshDrafts();
@@ -43,13 +43,13 @@
         },
         _onEditSurvey:function(survey){
-            Router.go('/survey/'+store.getIdentity(survey));
+            Router.go('/survey/'+survey._id);
         },
         _onPreviewSurvey:function(survey){
-            Router.go('/previewSurvey/'+store.getIdentity(survey));
+            Router.go('/previewSurvey/'+survey._id);
         },
         _onRunSurvey:function(survey){
-            var surveyRun = SurveyRun.create();
-            SurveyRun.Survey.set(surveyRun,survey);
-            store.put(surveyRun)
+            var surveyRun = surveyRuns.create();
+            surveyRun.survey = survey;
+            surveyRuns.save(surveyRun)
             .then(lang.hitch(this,function(surveyRun){
                 this._onRunDetails(surveyRun);
@@ -59,5 +59,5 @@
         },
         _onRunDetails: function(surveyRun) {
-            Router.go('/surveyRun/'+store.getIdentity(surveyRun));
+            Router.go('/surveyRun/'+surveyRun._id);
         },
         refresh: function() {
@@ -68,10 +68,9 @@
         refreshDrafts: function() {
             this.draftsContainer.set('content','');
-            when(store.query("_design/surveys/_view/drafts"),
-                    lang.hitch(this,function(surveys) {
+            when(surveys.query({drafts:true}), lang.hitch(this,function(surveys) {
                 this.draftsTab.set('title','Drafts ('+surveys.length+')');
                 array.forEach(surveys,function(survey){
                     var w = new LineWithActionsWidget({
-                        title: Survey.DisplayTitle.get(survey) || '(unnamed)',
+                        title: survey.title || '(unnamed)',
                         actions: [{
                             callback: lang.hitch(this,'_onPublishSurvey',survey),
@@ -110,10 +109,9 @@
         refreshPublished: function() {
             this.publishedContainer.set('content','');
-            when(store.query("_design/surveys/_view/published"),
-                    lang.hitch(this, function(surveys) {
+            when(surveys.query({published:true}), lang.hitch(this, function(surveys) {
                 this.publishedTab.set('title','Published ('+surveys.length+')');
                 array.forEach(surveys,function(survey){
                     var w = new LineWithActionsWidget({
-                        title: Survey.DisplayTitle.get(survey),
+                        title: survey.title || "",
                         actions:[{
                             callback: lang.hitch(this,'_onPreviewSurvey',survey),
@@ -138,10 +136,9 @@
         refreshRuns: function() {
             this.runsContainer.set('content','');
-            when(store.query("_design/default/_view/by_type",{key:'SurveyRun'}),
-                    lang.hitch(this,function(surveyRuns){
+            when(surveyRuns.query(), lang.hitch(this,function(surveyRuns){
                 this.runsTab.set('title','Runs ('+surveyRuns.length+')');
                 array.forEach(surveyRuns,function(surveyRun){
                     var w = new LineWithActionsWidget({
-                        title: SurveyRun.DisplayTitle.get(surveyRun),
+                        title: surveyRun.title || "",
                         actions:[{
                             callback: lang.hitch(this,'_onRunDetails',surveyRun),
Index: Dev/trunk/src/client/qed-client/request.js
===================================================================
--- Dev/trunk/src/client/qed-client/request.js	(revision 486)
+++ 	(revision )
@@ -1,68 +1,0 @@
-define([
-    "./session",
-    "dojo/Deferred",
-    "dojo/on",
-    "dojo/request"
-], function(session, Deferred, on, request) {
-
-    var user = session.get();
-    var queue = [];
-    
-    on(session, 'change', function(newUser) {
-        user = newUser;
-        if ( user ) {
-            retry();
-        }
-    });
-    
-    function retry() {
-        if (queue.length > 0) {
-            var item = queue.shift();
-            console.log("Retry",item.url);
-            real_request(item);
-        }
-    }
-    
-    function real_request(item) {
-        var req = request(item.url,item.options);
-
-        // forward successfull response
-        req.then(function(body){
-            item.dfd.resolve(body);
-        });
-
-        // handle unauthenticated and queued requests
-        req.response.then(function(response){
-            retry();
-        }, function(error) {
-            if ( error.response.status === 401 ) {
-                queue.unshift(item);
-                session.restore();
-            } else {
-                item.dfd.reject(error); // this should be error body
-                                        // not, the request?
-                retry();
-            }
-        });
-    }
-    
-    var _request = function(url, options) {
-        var item = {
-            url: url,
-            options: options,
-            dfd: new Deferred()
-        };
-        // only do the request directly if we are authenticated and
-        // there are no earlier requests queued.
-        if ( user && queue.length === 0 ) {
-            console.log("Request",url);
-            real_request(item);
-        } else {
-            console.log("Push",url);
-            queue.push(item);
-        }
-        return item.dfd.promise;
-    };
-
-    return _request;
-});
Index: Dev/trunk/src/client/qed-client/response.js
===================================================================
--- Dev/trunk/src/client/qed-client/response.js	(revision 486)
+++ Dev/trunk/src/client/qed-client/response.js	(revision 487)
@@ -4,8 +4,7 @@
     "./app/Path",
     "./lib/async",
-    "./model/classes/Response",
-    "./model/classes/SurveyRun",
+    "./model/classes/responses",
+    "./model/classes/surveyRuns",
     "./pages/response",
-    "./store",
     "dojo/_base/json",
     "dojo/date",
@@ -16,5 +15,5 @@
     "./stddeps",
     "dojo/domReady!"
-], function(Content, Page, Path, async, Response, SurveyRun, ResponsePage, store, json, date, locale, hash, parser, request) {
+], function(Content, Page, Path, async, responses, surveyRuns, ResponsePage, json, date, locale, hash, parser, request) {
     parser.parse();
     Content.startup();
@@ -26,63 +25,27 @@
     }
 
-    var path = new Path('/:surveyRunId');
+    var path = new Path('/:type/:id');
     var params = path.match(hash());
     params.options = params.options || {};
-
-    if ( !params || !params.surveyRunId ) {
+    
+    if ( params && params.type === 'surveyRuns' ) {
+        var response = responses.create();
+        response.surveyRunId = params.id;
+        responses.postWithSecret(response,params.options.secret)
+        .then(setContent,function(err){ error(err.error); });
+    } else if ( params && params.type === 'responses' ) {
+        responses.getWithSecret(params.id,params.options.secret)
+        .then(setContent,function(err){ error(err.error); });
+    } else {
         error("Something is wrong with the URL, don't know what survey to show you. Sorry.");
-        return;
     }
 
-    var surveyRunId = params.surveyRunId;
-
-    function checkDates(surveyRun) {
-        var now = new Date();
-        var startDate = SurveyRun.StartDate.get(surveyRun);
-        var endDate = SurveyRun.EndDate.get(surveyRun);
-        if ( startDate && date.compare(startDate,now) > 0 ) {
-            error("This survey will start on "+locale.format(startDate,'date'));
-            throw false;
-        }
-        if ( endDate && date.compare(now,endDate) > 0 ) {
-            error("This survey ended on "+locale.format(endDate,'date'));
-            throw false;
-        }
+    function setContent(response) {
+        hash(Path.format("/responses/"+responses.getId(response),
+                         {secret:response.secret}));
+        Content.set(new ResponsePage({
+            response: response
+        }));
     }
 
-    request.get('/api/surveyRuns/'+surveyRunId,{handleAs:'json'})
-    .then(function(surveyRun){
-        checkDates(surveyRun);
-        if ( params.options.id ) {
-            return params.options.id;
-        } else {
-            if ( surveyRun.mode === "open") {
-                var response = Response.create();
-                response.surveyRunId = surveyRunId;
-                return request.post('/api/responses',{
-                    handleAs:'json',
-                    data:json.toJson(response),
-                    headers:{"Content-Type": "application/json"}
-                }).then(function(res){
-                    return res.id;
-                });
-            } else {
-                error("Cannot respond to closed survey without response id. Sorry.");
-                throw false;
-            }
-        }
-        return surveyRun;
-    },function(){
-        error("No running survey found for the given id. Sorry.");
-        throw false;
-    })
-    .then(function(responseId){
-        hash(Path.format("/"+surveyRunId,{ id: responseId }));
-        Content.set(new ResponsePage({
-            surveyRunId: surveyRunId,
-            options: {
-                id: responseId
-            }
-        }));
-    });
 });
Index: Dev/trunk/src/client/qed-client/routes.js
===================================================================
--- Dev/trunk/src/client/qed-client/routes.js	(revision 486)
+++ Dev/trunk/src/client/qed-client/routes.js	(revision 487)
@@ -1,13 +1,11 @@
 define([
-    './pages/index',
-    './pages/questions',
-    './pages/question',
-    './pages/surveys',
-    './pages/survey',
-    './pages/surveyRun',
-    './pages/sessions',
-    './pages/session',
-    './pages/previewSurvey'
-],function(index,questions,question,surveys,survey,surveyRun,sessions,session,previewSurvey){
+    "./pages/index",
+    "./pages/previewSurvey",
+    "./pages/question",
+    "./pages/questions",
+    "./pages/survey",
+    "./pages/surveyRun",
+    "./pages/surveys"
+], function(index, previewSurvey, question, questions, survey, surveyRun, surveys) {
 
     return [
Index: Dev/trunk/src/client/qed-client/session.coffee
===================================================================
--- Dev/trunk/src/client/qed-client/session.coffee	(revision 486)
+++ Dev/trunk/src/client/qed-client/session.coffee	(revision 487)
@@ -2,7 +2,8 @@
     "dojo/_base/declare",
     "dojo/_base/json",
+    "dojo/Deferred",
     "dojo/Evented",
     "dojo/request"
-], (declare, json, Evented, request) ->
+], (declare, json, Deferred, Evented, request) ->
     Session = declare [Evented],
         info: null
@@ -18,5 +19,5 @@
                 @_set res
             , () =>
-                throw (@_set null)
+                new Deferred().reject (@_set null)
 
         login: (username, password) ->
@@ -30,5 +31,5 @@
                 @_set res
             , () =>
-                throw (@_set null)
+                new Deferred().reject (@_set null)
 
         logout: () ->
@@ -45,5 +46,5 @@
                 @info = newInfo
                 @emit 'change', @info
-                @info
+            @info
 
     new Session()
Index: Dev/trunk/src/client/qed-client/stddeps.js
===================================================================
--- Dev/trunk/src/client/qed-client/stddeps.js	(revision 486)
+++ Dev/trunk/src/client/qed-client/stddeps.js	(revision 487)
@@ -1,3 +1,4 @@
 define([
+
     // dijit & rft widgets used declaratively in templates
     'dijit/Dialog',
@@ -43,3 +44,4 @@
     './widgets/Selector',
     './widgets/TitleGroup'
+
 ],function(){});
Index: Dev/trunk/src/client/qed-client/store.js
===================================================================
--- Dev/trunk/src/client/qed-client/store.js	(revision 486)
+++ 	(revision )
@@ -1,47 +1,0 @@
-define([
-    'dojo/date/stamp',
-    'dojo/store/Cache',
-    'dojo/store/Memory',
-    'dojox/json/schema',
-    './model/schema',
-    './request',
-    './store/CouchStore'
-],function(stamp,Cache,Memory,jsonSchema,schema,request,CouchStore){
-    
-    var couchStore = new CouchStore({
-        target: '/data/',
-        request: request /*,
-        validate: function(object) {
-            var result = jsonSchema.validate(object,schema);
-            if ( result.valid ) {
-                return true;
-            } else {
-                console.log("Found error ",result," for invalid object ",object);
-                return false;
-            }
-        }*/
-    });
-    var memoryStore = new Memory({
-        idProperty: couchStore.idProperty
-    });
-    var cacheStore = new Cache(couchStore,memoryStore,{
-        isLoaded: function(object) {
-            // Unfortunately we cannot determine of the full documents were
-            // loaded or not. Therefore never cache query results.
-            return false;
-        }
-    });
-
-    var store = cacheStore;
-    store.formatDate = function(date){
-        return stamp.toISOString(date,{zulu:true});
-    };
-    store.parseDate = function(dateString){
-        return stamp.fromISOString(dateString);
-    };
-    store.timestamp = function() {
-        return this.formatDate(new Date());
-    };
-    return store;
-
-});
Index: Dev/trunk/src/client/qed-client/store/JsonRest.js
===================================================================
--- Dev/trunk/src/client/qed-client/store/JsonRest.js	(revision 487)
+++ Dev/trunk/src/client/qed-client/store/JsonRest.js	(revision 487)
@@ -0,0 +1,198 @@
+define(["../xhr", "dojo/_base/lang", "dojo/json", "dojo/io-query", "dojo/_base/declare", "dojo/store/util/QueryResults" /*=====, "./api/Store" =====*/
+], function(xhr, lang, JSON, ioQuery, declare, queryResults /*=====, Store =====*/){
+
+    // No base class, but for purposes of documentation, the base class is dojo/store/api/Store
+    var base = null;
+    /*===== base = Store; =====*/
+
+    /*=====
+    var __HeaderOptions = {
+            // headers: Object?
+            //        Additional headers to send along with the request.
+        },
+        __PutDirectives = declare(Store.PutDirectives, __HeaderOptions),
+        __QueryOptions = declare(Store.QueryOptions, __HeaderOptions);
+    =====*/
+
+    return declare("dojo.store.JsonRest", base, {
+        // summary:
+        //        This is a basic store for RESTful communicating with a server through JSON
+        //        formatted data. It implements dojo/store/api/Store.
+
+        constructor: function(options){
+            // summary:
+            //        This is a basic store for RESTful communicating with a server through JSON
+            //        formatted data.
+            // options: dojo/store/JsonRest
+            //        This provides any configuration information that will be mixed into the store
+            this.headers = {};
+            declare.safeMixin(this, options);
+        },
+
+        // headers: Object
+        //        Additional headers to pass in all requests to the server. These can be overridden
+        //        by passing additional headers to calls to the store.
+        headers: {},
+
+        // target: String
+        //        The target base URL to use for all requests to the server. This string will be
+        //        prepended to the id to generate the URL (relative or absolute) for requests
+        //        sent to the server
+        target: "",
+
+        // idProperty: String
+        //        Indicates the property to use as the identity property. The values of this
+        //        property should be unique.
+        idProperty: "id",
+
+        // sortParam: String
+        //        The query parameter to used for holding sort information. If this is omitted, than
+        //        the sort information is included in a functional query token to avoid colliding
+        //        with the set of name/value pairs.
+
+        // ascendingPrefix: String
+        //        The prefix to apply to sort attribute names that are ascending
+        ascendingPrefix: "+",
+
+        // descendingPrefix: String
+        //        The prefix to apply to sort attribute names that are ascending
+        descendingPrefix: "-",
+
+
+        get: function(id, options){
+            // summary:
+            //        Retrieves an object by its identity. This will trigger a GET request to the server using
+            //        the url `this.target + id`.
+            // id: Number
+            //        The identity to use to lookup the object
+            // options: Object?
+            //        HTTP headers. For consistency with other methods, if a `headers` key exists on this object, it will be
+            //        used to provide HTTP headers instead.
+            // returns: Object
+            //        The object in the store that matches the given id.
+            options = options || {};
+            var headers = lang.mixin({ Accept: this.accepts }, this.headers, options.headers || options);
+            return xhr("GET", {
+                url: this.target + id, 
+                handleAs: "json",
+                headers: headers
+            });
+        },
+
+        // accepts: String
+        //        Defines the Accept header to use on HTTP requests
+        accepts: "application/javascript, application/json",
+
+        getIdentity: function(object){
+            // summary:
+            //        Returns an object's identity
+            // object: Object
+            //        The object to get the identity from
+            // returns: Number
+            return object[this.idProperty];
+        },
+
+        put: function(object, options){
+            // summary:
+            //        Stores an object. This will trigger a PUT request to the server
+            //        if the object has an id, otherwise it will trigger a POST request.
+            // object: Object
+            //        The object to store.
+            // options: __PutDirectives?
+            //        Additional metadata for storing the data.  Includes an "id"
+            //        property if a specific id is to be used.
+            // returns: dojo/_base/Deferred
+            options = options || {};
+            var id = ("id" in options) ? options.id : this.getIdentity(object);
+            var hasId = typeof id !== "undefined";
+            return xhr(hasId && !options.incremental ? "PUT" : "POST", {
+                url: hasId ? this.target + id : this.target,
+                postData: JSON.stringify(object),
+                handleAs: "json",
+                headers: lang.mixin({
+                    "Content-Type": "application/json",
+                    Accept: this.accepts,
+                    "If-Match": options.overwrite === true ? "*" : null,
+                    "If-None-Match": options.overwrite === false ? "*" : null
+                }, this.headers, options.headers)
+            });
+        },
+
+        add: function(object, options){
+            // summary:
+            //        Adds an object. This will trigger a PUT request to the server
+            //        if the object has an id, otherwise it will trigger a POST request.
+            // object: Object
+            //        The object to store.
+            // options: __PutDirectives?
+            //        Additional metadata for storing the data.  Includes an "id"
+            //        property if a specific id is to be used.
+            options = options || {};
+            options.overwrite = false;
+            return this.put(object, options);
+        },
+
+        remove: function(id, options){
+            // summary:
+            //        Deletes an object by its identity. This will trigger a DELETE request to the server.
+            // id: Number
+            //        The identity to use to delete the object
+            // options: __HeaderOptions?
+            //        HTTP headers.
+            options = options || {};
+            return xhr("DELETE", {
+                url: this.target + id,
+                headers: lang.mixin({}, this.headers, options.headers)
+            });
+        },
+
+        query: function(query, options){
+            // summary:
+            //        Queries the store for objects. This will trigger a GET request to the server, with the
+            //        query added as a query string.
+            // query: Object
+            //        The query to use for retrieving objects from the store.
+            // options: __QueryOptions?
+            //        The optional arguments to apply to the resultset.
+            // returns: dojo/store/api/Store.QueryResults
+            //        The results of the query, extended with iterative methods.
+            options = options || {};
+
+            var headers = lang.mixin({ Accept: this.accepts }, this.headers, options.headers);
+
+            if(options.start >= 0 || options.count >= 0){
+                //set X-Range for Opera since it blocks "Range" header
+                headers.Range = headers["X-Range"] = "items=" + (options.start || '0') + '-' +
+                    (("count" in options && options.count !== Infinity) ?
+                        (options.count + (options.start || 0) - 1) : '');
+            }
+            var hasQuestionMark = this.target.indexOf("?") > -1;
+            if(query && typeof query === "object"){
+                query = ioQuery.objectToQuery(query);
+                query = query ? (hasQuestionMark ? "&" : "?") + query: "";
+            }
+            if(options && options.sort){
+                var sortParam = this.sortParam;
+                query += (query || hasQuestionMark ? "&" : "?") + (sortParam ? sortParam + '=' : "sort(");
+                for(var i = 0; i<options.sort.length; i++){
+                    var sort = options.sort[i];
+                    query += (i > 0 ? "," : "") + (sort.descending ? this.descendingPrefix : this.ascendingPrefix) + encodeURIComponent(sort.attribute);
+                }
+                if(!sortParam){
+                    query += ")";
+                }
+            }
+            var results = xhr("GET", {
+                url: this.target + (query || ""),
+                handleAs: "json",
+                headers: headers
+            });
+            results.total = results.then(function(){
+                var range = results.ioArgs.xhr.getResponseHeader("Content-Range");
+                return range && (range = range.match(/\/(.*)/)) && +range[1];
+            });
+            return queryResults(results);
+        }
+    });
+
+});
Index: Dev/trunk/src/client/qed-client/ui/LoginDialogWrapper.coffee
===================================================================
--- Dev/trunk/src/client/qed-client/ui/LoginDialogWrapper.coffee	(revision 486)
+++ Dev/trunk/src/client/qed-client/ui/LoginDialogWrapper.coffee	(revision 487)
@@ -17,5 +17,5 @@
             if @_started then return
             @inherited arguments
-            _on session, 'change', (lang.hitch @, @onUserChange)
+            _on session, 'change', (lang.hitch @, 'onUserChange')
             @onUserChange session.get()
         onLogin: (evt) ->
@@ -35,2 +35,3 @@
             else
                 @loginDialog.show()
+            null
Index: Dev/trunk/src/client/qed-client/xhr.js
===================================================================
--- Dev/trunk/src/client/qed-client/xhr.js	(revision 487)
+++ Dev/trunk/src/client/qed-client/xhr.js	(revision 487)
@@ -0,0 +1,65 @@
+define([
+    "./session",
+    "dojo/Deferred",
+    "dojo/_base/lang",
+    "dojo/on",
+    "dojo/_base/xhr"
+], function(session, Deferred, lang, on, xhr) {
+
+    var user = session.get();
+    var queue = [];
+    
+    on(session, 'change', function(newUser){
+        user = newUser;
+        retry();
+    });
+
+    function retry() {
+        if (user && queue.length > 0) {
+            var item = queue.shift();
+            console.log("Retry",item.options.url);
+            real_request(item);
+        }
+    }
+    
+    function real_request(item) {
+        var req = xhr(item.method,lang.mixin(item.options||{},{
+            failOk: true
+        }));
+        item.promise.ioArgs = req.ioArgs;
+
+        req.then(function(result){
+            item.dfd.resolve(result);
+            retry();
+        }, function(error){
+            if ( error.response.status === 401 ) {
+                queue.unshift(item);
+                session.restore();
+            } else {
+                item.dfd.reject(error);
+                retry();
+            }
+        });
+    }
+    
+    var _request = function(method, options) {
+        var item = {
+            method: method,
+            options: options,
+            dfd: new Deferred()
+        };
+        item.promise = lang.delegate(item.dfd.promise);
+        // only do the request directly if we are authenticated and
+        // there are no earlier requests queued.
+        if ( user && queue.length === 0 ) {
+            console.log("Request",options.url);
+            real_request(item);
+        } else {
+            console.log("Push",options.url);
+            queue.push(item);
+        }
+        return item.promise;
+    };
+
+    return _request;
+});
Index: Dev/trunk/src/node_modules/express/node_modules/buffer-crc32/package.json
===================================================================
--- Dev/trunk/src/node_modules/express/node_modules/buffer-crc32/package.json	(revision 486)
+++ Dev/trunk/src/node_modules/express/node_modules/buffer-crc32/package.json	(revision 487)
@@ -20,5 +20,5 @@
   "main": "index.js",
   "scripts": {
-    "test": "./node_modules/.bin/tap tests/*.test.js"
+    "test": "tap tests/*.test.js"
   },
   "dependencies": {},
@@ -36,4 +36,8 @@
   },
   "_id": "buffer-crc32@0.2.1",
-  "_from": "buffer-crc32@0.2.1"
+  "dist": {
+    "shasum": "d4831cc88b961550a88d829efbc0b139caf22d8c"
+  },
+  "_from": "buffer-crc32@0.2.1",
+  "_resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.1.tgz"
 }
Index: Dev/trunk/src/node_modules/express/node_modules/commander/package.json
===================================================================
--- Dev/trunk/src/node_modules/express/node_modules/commander/package.json	(revision 486)
+++ Dev/trunk/src/node_modules/express/node_modules/commander/package.json	(revision 487)
@@ -34,5 +34,10 @@
     "url": "https://github.com/visionmedia/commander.js/issues"
   },
+  "homepage": "https://github.com/visionmedia/commander.js",
   "_id": "commander@0.6.1",
-  "_from": "commander@0.6.1"
+  "dist": {
+    "shasum": "c725ed5e9b2bf532b3fe3cba4f81b552cecf0550"
+  },
+  "_from": "commander@0.6.1",
+  "_resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz"
 }
Index: Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/bytes/package.json
===================================================================
--- Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/bytes/package.json	(revision 486)
+++ Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/bytes/package.json	(revision 487)
@@ -17,4 +17,8 @@
   "readmeFilename": "Readme.md",
   "_id": "bytes@0.2.0",
-  "_from": "bytes@0.2.0"
+  "dist": {
+    "shasum": "b4c569295d86a498a119945f1d8a26f76e4b5462"
+  },
+  "_from": "bytes@0.2.0",
+  "_resolved": "https://registry.npmjs.org/bytes/-/bytes-0.2.0.tgz"
 }
Index: Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/cookie/package.json
===================================================================
--- Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/cookie/package.json	(revision 486)
+++ Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/cookie/package.json	(revision 487)
@@ -32,5 +32,10 @@
     "url": "https://github.com/shtylman/node-cookie/issues"
   },
+  "homepage": "https://github.com/shtylman/node-cookie",
   "_id": "cookie@0.0.5",
-  "_from": "cookie@0.0.5"
+  "dist": {
+    "shasum": "2597397e06ec3eaf53c88851570e97949c1e4dcd"
+  },
+  "_from": "cookie@0.0.5",
+  "_resolved": "https://registry.npmjs.org/cookie/-/cookie-0.0.5.tgz"
 }
Index: Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/formidable/package.json
===================================================================
--- Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/formidable/package.json	(revision 486)
+++ Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/formidable/package.json	(revision 487)
@@ -35,4 +35,8 @@
   "dependencies": {},
   "_id": "formidable@1.0.14",
-  "_from": "formidable@1.0.14"
+  "dist": {
+    "shasum": "08be7c89a9ebffbe5b2fbd4df0ad3f5cd19c5147"
+  },
+  "_from": "formidable@1.0.14",
+  "_resolved": "https://registry.npmjs.org/formidable/-/formidable-1.0.14.tgz"
 }
Index: Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/pause/package.json
===================================================================
--- Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/pause/package.json	(revision 486)
+++ Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/pause/package.json	(revision 487)
@@ -17,4 +17,8 @@
   "readmeFilename": "Readme.md",
   "_id": "pause@0.0.1",
-  "_from": "pause@0.0.1"
+  "dist": {
+    "shasum": "dc3b2287742f3f6249f2d228e74cff5a3f9fe8da"
+  },
+  "_from": "pause@0.0.1",
+  "_resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz"
 }
Index: Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/qs/package.json
===================================================================
--- Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/qs/package.json	(revision 486)
+++ Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/qs/package.json	(revision 487)
@@ -33,5 +33,10 @@
     "url": "https://github.com/visionmedia/node-querystring/issues"
   },
+  "homepage": "https://github.com/visionmedia/node-querystring",
   "_id": "qs@0.6.5",
-  "_from": "qs@0.6.5"
+  "dist": {
+    "shasum": "cdaafb6ed1dbcf48405f41ca92bf95361ad923dc"
+  },
+  "_from": "qs@0.6.5",
+  "_resolved": "https://registry.npmjs.org/qs/-/qs-0.6.5.tgz"
 }
Index: Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/send/node_modules/mime/README.md
===================================================================
--- Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/send/node_modules/mime/README.md	(revision 486)
+++ Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/send/node_modules/mime/README.md	(revision 487)
@@ -12,5 +12,5 @@
 
 ### mime.lookup(path)
-Get the mime type associated with a file. Performs a case-insensitive lookup using the extension in `path` (the substring after the last '/' or '.').  E.g.
+Get the mime type associated with a file, if no mime type is found `application/octet-stream` is returned. Performs a case-insensitive lookup using the extension in `path` (the substring after the last '/' or '.').  E.g.
 
     var mime = require('mime');
@@ -20,4 +20,7 @@
     mime.lookup('.TXT');                      // => 'text/plain'
     mime.lookup('htm');                       // => 'text/html'
+
+### mime.default_type
+Sets the mime type returned when `mime.lookup` fails to find the extension searched for. (Default is `application/octet-stream`.)
 
 ### mime.extension(type)
Index: Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/send/node_modules/mime/mime.js
===================================================================
--- Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/send/node_modules/mime/mime.js	(revision 486)
+++ Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/send/node_modules/mime/mime.js	(revision 487)
@@ -70,5 +70,5 @@
  */
 Mime.prototype.lookup = function(path, fallback) {
-  var ext = path.replace(/.*[\.\/]/, '').toLowerCase();
+  var ext = path.replace(/.*[\.\/\\]/, '').toLowerCase();
 
   return this.types[ext] || fallback || this.default_type;
@@ -79,5 +79,6 @@
  */
 Mime.prototype.extension = function(mimeType) {
-  return this.extensions[mimeType];
+  var type = mimeType.match(/^\s*([^;\s]*)(?:;|\s|$)/)[1].toLowerCase();
+  return this.extensions[type];
 };
 
Index: Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/send/node_modules/mime/package.json
===================================================================
--- Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/send/node_modules/mime/package.json	(revision 486)
+++ Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/send/node_modules/mime/package.json	(revision 487)
@@ -25,11 +25,12 @@
     "type": "git"
   },
-  "version": "1.2.9",
-  "readme": "# mime\n\nComprehensive MIME type mapping API. Includes all 600+ types and 800+ extensions defined by the Apache project, plus additional types submitted by the node.js community.\n\n## Install\n\nInstall with [npm](http://github.com/isaacs/npm):\n\n    npm install mime\n\n## API - Queries\n\n### mime.lookup(path)\nGet the mime type associated with a file. Performs a case-insensitive lookup using the extension in `path` (the substring after the last '/' or '.').  E.g.\n\n    var mime = require('mime');\n\n    mime.lookup('/path/to/file.txt');         // => 'text/plain'\n    mime.lookup('file.txt');                  // => 'text/plain'\n    mime.lookup('.TXT');                      // => 'text/plain'\n    mime.lookup('htm');                       // => 'text/html'\n\n### mime.extension(type)\nGet the default extension for `type`\n\n    mime.extension('text/html');                 // => 'html'\n    mime.extension('application/octet-stream');  // => 'bin'\n\n### mime.charsets.lookup()\n\nMap mime-type to charset\n\n    mime.charsets.lookup('text/plain');        // => 'UTF-8'\n\n(The logic for charset lookups is pretty rudimentary.  Feel free to suggest improvements.)\n\n## API - Defining Custom Types\n\nThe following APIs allow you to add your own type mappings within your project.  If you feel a type should be included as part of node-mime, see [requesting new types](https://github.com/broofa/node-mime/wiki/Requesting-New-Types).\n\n### mime.define()\n\nAdd custom mime/extension mappings\n\n    mime.define({\n        'text/x-some-format': ['x-sf', 'x-sft', 'x-sfml'],\n        'application/x-my-type': ['x-mt', 'x-mtt'],\n        // etc ...\n    });\n\n    mime.lookup('x-sft');                 // => 'text/x-some-format'\n\nThe first entry in the extensions array is returned by `mime.extension()`. E.g.\n\n    mime.extension('text/x-some-format'); // => 'x-sf'\n\n### mime.load(filepath)\n\nLoad mappings from an Apache \".types\" format file\n\n    mime.load('./my_project.types');\n\nThe .types file format is simple -  See the `types` dir for examples.\n",
+  "version": "1.2.11",
+  "readme": "# mime\n\nComprehensive MIME type mapping API. Includes all 600+ types and 800+ extensions defined by the Apache project, plus additional types submitted by the node.js community.\n\n## Install\n\nInstall with [npm](http://github.com/isaacs/npm):\n\n    npm install mime\n\n## API - Queries\n\n### mime.lookup(path)\nGet the mime type associated with a file, if no mime type is found `application/octet-stream` is returned. Performs a case-insensitive lookup using the extension in `path` (the substring after the last '/' or '.').  E.g.\n\n    var mime = require('mime');\n\n    mime.lookup('/path/to/file.txt');         // => 'text/plain'\n    mime.lookup('file.txt');                  // => 'text/plain'\n    mime.lookup('.TXT');                      // => 'text/plain'\n    mime.lookup('htm');                       // => 'text/html'\n\n### mime.default_type\nSets the mime type returned when `mime.lookup` fails to find the extension searched for. (Default is `application/octet-stream`.)\n\n### mime.extension(type)\nGet the default extension for `type`\n\n    mime.extension('text/html');                 // => 'html'\n    mime.extension('application/octet-stream');  // => 'bin'\n\n### mime.charsets.lookup()\n\nMap mime-type to charset\n\n    mime.charsets.lookup('text/plain');        // => 'UTF-8'\n\n(The logic for charset lookups is pretty rudimentary.  Feel free to suggest improvements.)\n\n## API - Defining Custom Types\n\nThe following APIs allow you to add your own type mappings within your project.  If you feel a type should be included as part of node-mime, see [requesting new types](https://github.com/broofa/node-mime/wiki/Requesting-New-Types).\n\n### mime.define()\n\nAdd custom mime/extension mappings\n\n    mime.define({\n        'text/x-some-format': ['x-sf', 'x-sft', 'x-sfml'],\n        'application/x-my-type': ['x-mt', 'x-mtt'],\n        // etc ...\n    });\n\n    mime.lookup('x-sft');                 // => 'text/x-some-format'\n\nThe first entry in the extensions array is returned by `mime.extension()`. E.g.\n\n    mime.extension('text/x-some-format'); // => 'x-sf'\n\n### mime.load(filepath)\n\nLoad mappings from an Apache \".types\" format file\n\n    mime.load('./my_project.types');\n\nThe .types file format is simple -  See the `types` dir for examples.\n",
   "readmeFilename": "README.md",
   "bugs": {
     "url": "https://github.com/broofa/node-mime/issues"
   },
-  "_id": "mime@1.2.9",
+  "homepage": "https://github.com/broofa/node-mime",
+  "_id": "mime@1.2.11",
   "_from": "mime@~1.2.9"
 }
Index: Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/send/node_modules/mime/test.js
===================================================================
--- Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/send/node_modules/mime/test.js	(revision 486)
+++ Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/send/node_modules/mime/test.js	(revision 487)
@@ -5,4 +5,5 @@
 var mime = require('./mime');
 var assert = require('assert');
+var path = require('path');
 
 function eq(a, b) {
@@ -18,14 +19,14 @@
 //
 
-eq('text/plain', mime.lookup('text.txt'));
-eq('text/plain', mime.lookup('.text.txt'));
-eq('text/plain', mime.lookup('.txt'));
-eq('text/plain', mime.lookup('txt'));
-eq('application/octet-stream', mime.lookup('text.nope'));
-eq('fallback', mime.lookup('text.fallback', 'fallback'));
-eq('application/octet-stream', mime.lookup('constructor'));
-eq('text/plain', mime.lookup('TEXT.TXT'));
-eq('text/event-stream', mime.lookup('text/event-stream'));
-eq('application/x-web-app-manifest+json', mime.lookup('text.webapp'));
+eq('text/plain', mime.lookup('text.txt'));     // normal file
+eq('text/plain', mime.lookup('TEXT.TXT'));     // uppercase
+eq('text/plain', mime.lookup('dir/text.txt')); // dir + file
+eq('text/plain', mime.lookup('.text.txt'));    // hidden file
+eq('text/plain', mime.lookup('.txt'));         // nameless
+eq('text/plain', mime.lookup('txt'));          // extension-only
+eq('text/plain', mime.lookup('/txt'));         // extension-less ()
+eq('text/plain', mime.lookup('\\txt'));        // Windows, extension-less
+eq('application/octet-stream', mime.lookup('text.nope')); // unrecognized
+eq('fallback', mime.lookup('text.fallback', 'fallback')); // alternate default
 
 //
@@ -36,12 +37,21 @@
 eq('html', mime.extension(mime.types.htm));
 eq('bin', mime.extension('application/octet-stream'));
-eq(undefined, mime.extension('constructor'));
+eq('bin', mime.extension('application/octet-stream '));
+eq('html', mime.extension(' text/html; charset=UTF-8'));
+eq('html', mime.extension('text/html; charset=UTF-8 '));
+eq('html', mime.extension('text/html; charset=UTF-8'));
+eq('html', mime.extension('text/html ; charset=UTF-8'));
+eq('html', mime.extension('text/html;charset=UTF-8'));
+eq('html', mime.extension('text/Html;charset=UTF-8'));
+eq(undefined, mime.extension('unrecognized'));
 
 //
-// Test node types
+// Test node.types lookups
 //
 
+eq('application/font-woff', mime.lookup('file.woff'));
 eq('application/octet-stream', mime.lookup('file.buffer'));
 eq('audio/mp4', mime.lookup('file.m4a'));
+eq('font/opentype', mime.lookup('file.otf'));
 
 //
@@ -53,3 +63,22 @@
 eq('fallback', mime.charsets.lookup('application/octet-stream', 'fallback'));
 
+//
+// Test for overlaps between mime.types and node.types
+//
+
+var apacheTypes = new mime.Mime(), nodeTypes = new mime.Mime();
+apacheTypes.load(path.join(__dirname, 'types/mime.types'));
+nodeTypes.load(path.join(__dirname, 'types/node.types'));
+
+var keys = [].concat(Object.keys(apacheTypes.types))
+             .concat(Object.keys(nodeTypes.types));
+keys.sort();
+for (var i = 1; i < keys.length; i++) {
+  if (keys[i] == keys[i-1]) {
+    console.warn('Warning: ' +
+      'node.types defines ' + keys[i] + '->' + nodeTypes.types[keys[i]] +
+      ', mime.types defines ' + keys[i] + '->' + apacheTypes.types[keys[i]]);
+  }
+}
+
 console.log('\nOK');
Index: Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/send/node_modules/mime/types/mime.types
===================================================================
--- Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/send/node_modules/mime/types/mime.types	(revision 486)
+++ Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/send/node_modules/mime/types/mime.types	(revision 487)
@@ -1055,5 +1055,5 @@
 application/x-font-ttf				ttf ttc
 application/x-font-type1			pfa pfb pfm afm
-application/x-font-woff				woff
+application/font-woff				woff
 # application/x-font-vfont
 application/x-freearc				arc
Index: Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/send/node_modules/mime/types/node.types
===================================================================
--- Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/send/node_modules/mime/types/node.types	(revision 486)
+++ Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/send/node_modules/mime/types/node.types	(revision 487)
@@ -16,9 +16,9 @@
 text/x-component  htc
 
-# What: HTML5 application cache manifest
+# What: HTML5 application cache manifes ('.manifest' extension)
 # Why: De-facto standard. Required by Mozilla browser when serving HTML5 apps
 # per https://developer.mozilla.org/en/offline_resources_in_firefox
 # Added by: louisremi
-text/cache-manifest  appcache manifest
+text/cache-manifest  manifest
 
 # What: node binary buffer format
@@ -59,2 +59,19 @@
 # Added by: avoidwork
 text/x-markdown  markdown md mkd
+
+# What: ini files
+# Why: because they're just text files
+# Added by: Matthew Kastor
+text/plain  ini
+
+# What: DASH Adaptive Streaming manifest
+# Why: https://developer.mozilla.org/en-US/docs/DASH_Adaptive_Streaming_for_HTML_5_Video
+# Added by: eelcocramer
+application/dash+xml mdp
+
+# What: OpenType font files - http://www.microsoft.com/typography/otspec/
+# Why:  Browsers usually ignore the font MIME types and sniff the content,
+#       but Chrome, shows a warning if OpenType fonts aren't served with
+#       the `font/opentype` MIME type: http://i.imgur.com/8c5RN8M.png.
+# Added by: alrra
+font/opentype  otf
Index: Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/send/package.json
===================================================================
--- Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/send/package.json	(revision 486)
+++ Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/send/package.json	(revision 487)
@@ -37,5 +37,10 @@
     "url": "https://github.com/visionmedia/send/issues"
   },
+  "homepage": "https://github.com/visionmedia/send",
   "_id": "send@0.1.1",
-  "_from": "send@0.1.1"
+  "dist": {
+    "shasum": "40ded726322604c29d7229683f9207bd6d76e217"
+  },
+  "_from": "send@0.1.1",
+  "_resolved": "https://registry.npmjs.org/send/-/send-0.1.1.tgz"
 }
Index: Dev/trunk/src/node_modules/express/node_modules/connect/package.json
===================================================================
--- Dev/trunk/src/node_modules/express/node_modules/connect/package.json	(revision 486)
+++ Dev/trunk/src/node_modules/express/node_modules/connect/package.json	(revision 487)
@@ -49,5 +49,10 @@
     "url": "https://github.com/senchalabs/connect/issues"
   },
+  "homepage": "https://github.com/senchalabs/connect",
   "_id": "connect@2.7.11",
-  "_from": "connect@2.7.11"
+  "dist": {
+    "shasum": "839e3928b727827db859a4b3a1d04ea9a06b6ab1"
+  },
+  "_from": "connect@2.7.11",
+  "_resolved": "https://registry.npmjs.org/connect/-/connect-2.7.11.tgz"
 }
Index: Dev/trunk/src/node_modules/express/node_modules/cookie-signature/package.json
===================================================================
--- Dev/trunk/src/node_modules/express/node_modules/cookie-signature/package.json	(revision 486)
+++ Dev/trunk/src/node_modules/express/node_modules/cookie-signature/package.json	(revision 487)
@@ -21,4 +21,8 @@
   "readmeFilename": "Readme.md",
   "_id": "cookie-signature@1.0.1",
-  "_from": "cookie-signature@1.0.1"
+  "dist": {
+    "shasum": "404efcace10fb30f3451483045d743463ae8602c"
+  },
+  "_from": "cookie-signature@1.0.1",
+  "_resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.1.tgz"
 }
Index: Dev/trunk/src/node_modules/express/node_modules/cookie/package.json
===================================================================
--- Dev/trunk/src/node_modules/express/node_modules/cookie/package.json	(revision 486)
+++ Dev/trunk/src/node_modules/express/node_modules/cookie/package.json	(revision 487)
@@ -32,5 +32,10 @@
     "url": "https://github.com/shtylman/node-cookie/issues"
   },
+  "homepage": "https://github.com/shtylman/node-cookie",
   "_id": "cookie@0.1.0",
-  "_from": "cookie@0.1.0"
+  "dist": {
+    "shasum": "b56ac6108fcab9073208d405c4fa8aaa35f00810"
+  },
+  "_from": "cookie@0.1.0",
+  "_resolved": "https://registry.npmjs.org/cookie/-/cookie-0.1.0.tgz"
 }
Index: Dev/trunk/src/node_modules/express/node_modules/debug/Readme.md
===================================================================
--- Dev/trunk/src/node_modules/express/node_modules/debug/Readme.md	(revision 486)
+++ Dev/trunk/src/node_modules/express/node_modules/debug/Readme.md	(revision 487)
@@ -1,3 +1,2 @@
-
 # debug
 
@@ -59,8 +58,9 @@
   ![](http://f.cl.ly/items/2i3h1d3t121M2Z1A3Q0N/Screenshot.png)
 
-  When stdout is not a TTY, `Date#toUTCString()` is used, making it more useful for logging the debug information as shown below:
+  When stderr is not a TTY, `Date#toUTCString()` is used, making it more useful for logging the debug information as shown below:
+  _(NOTE: Debug now uses stderr instead of stdout, so the correct shell command for this example is actually `DEBUG=* node example/worker 2> out &`)_
   
   ![](http://f.cl.ly/items/112H3i0e0o0P0a2Q2r11/Screenshot.png)
-
+  
 ## Conventions
 
Index: Dev/trunk/src/node_modules/express/node_modules/debug/debug.js
===================================================================
--- Dev/trunk/src/node_modules/express/node_modules/debug/debug.js	(revision 486)
+++ Dev/trunk/src/node_modules/express/node_modules/debug/debug.js	(revision 487)
@@ -18,4 +18,6 @@
 
   return function(fmt){
+    fmt = coerce(fmt);
+
     var curr = new Date;
     var ms = curr - (debug[name] || curr);
@@ -120,5 +122,16 @@
 };
 
+/**
+ * Coerce `val`.
+ */
+
+function coerce(val) {
+  if (val instanceof Error) return val.stack || val.message;
+  return val;
+}
+
 // persist
 
-if (window.localStorage) debug.enable(localStorage.debug);
+try {
+  if (window.localStorage) debug.enable(localStorage.debug);
+} catch(e){}
Index: Dev/trunk/src/node_modules/express/node_modules/debug/lib/debug.js
===================================================================
--- Dev/trunk/src/node_modules/express/node_modules/debug/lib/debug.js	(revision 486)
+++ Dev/trunk/src/node_modules/express/node_modules/debug/lib/debug.js	(revision 487)
@@ -109,4 +109,6 @@
 
   function colored(fmt) {
+    fmt = coerce(fmt);
+
     var curr = new Date;
     var ms = curr - (prev[name] || curr);
@@ -122,4 +124,6 @@
 
   function plain(fmt) {
+    fmt = coerce(fmt);
+
     fmt = new Date().toUTCString()
       + ' ' + name + ' ' + fmt;
@@ -133,2 +137,11 @@
     : plain;
 }
+
+/**
+ * Coerce `val`.
+ */
+
+function coerce(val) {
+  if (val instanceof Error) return val.stack || val.message;
+  return val;
+}
Index: Dev/trunk/src/node_modules/express/node_modules/debug/package.json
===================================================================
--- Dev/trunk/src/node_modules/express/node_modules/debug/package.json	(revision 486)
+++ Dev/trunk/src/node_modules/express/node_modules/debug/package.json	(revision 487)
@@ -1,5 +1,5 @@
 {
   "name": "debug",
-  "version": "0.7.2",
+  "version": "0.7.4",
   "repository": {
     "type": "git",
@@ -21,8 +21,13 @@
   },
   "main": "lib/debug.js",
-  "browserify": "debug.js",
+  "browser": "./debug.js",
   "engines": {
     "node": "*"
   },
+  "files": [
+    "lib/debug.js",
+    "debug.js",
+    "index.js"
+  ],
   "component": {
     "scripts": {
@@ -31,11 +36,11 @@
     }
   },
-  "readme": "\n# debug\n\n  tiny node.js debugging utility modelled after node core's debugging technique.\n\n## Installation\n\n```\n$ npm install debug\n```\n\n## Usage\n\n With `debug` you simply invoke the exported function to generate your debug function, passing it a name which will determine if a noop function is returned, or a decorated `console.error`, so all of the `console` format string goodies you're used to work fine. A unique color is selected per-function for visibility.\n \nExample _app.js_:\n\n```js\nvar debug = require('debug')('http')\n  , http = require('http')\n  , name = 'My App';\n\n// fake app\n\ndebug('booting %s', name);\n\nhttp.createServer(function(req, res){\n  debug(req.method + ' ' + req.url);\n  res.end('hello\\n');\n}).listen(3000, function(){\n  debug('listening');\n});\n\n// fake worker of some kind\n\nrequire('./worker');\n```\n\nExample _worker.js_:\n\n```js\nvar debug = require('debug')('worker');\n\nsetInterval(function(){\n  debug('doing some work');\n}, 1000);\n```\n\n The __DEBUG__ environment variable is then used to enable these based on space or comma-delimited names. Here are some examples:\n\n  ![debug http and worker](http://f.cl.ly/items/18471z1H402O24072r1J/Screenshot.png)\n\n  ![debug worker](http://f.cl.ly/items/1X413v1a3M0d3C2c1E0i/Screenshot.png)\n\n## Millisecond diff\n\n  When actively developing an application it can be useful to see when the time spent between one `debug()` call and the next. Suppose for example you invoke `debug()` before requesting a resource, and after as well, the \"+NNNms\" will show you how much time was spent between calls.\n\n  ![](http://f.cl.ly/items/2i3h1d3t121M2Z1A3Q0N/Screenshot.png)\n\n  When stdout is not a TTY, `Date#toUTCString()` is used, making it more useful for logging the debug information as shown below:\n  \n  ![](http://f.cl.ly/items/112H3i0e0o0P0a2Q2r11/Screenshot.png)\n\n## Conventions\n\n If you're using this in one or more of your libraries, you _should_ use the name of your library so that developers may toggle debugging as desired without guessing names. If you have more than one debuggers you _should_ prefix them with your library name and use \":\" to separate features. For example \"bodyParser\" from Connect would then be \"connect:bodyParser\". \n\n## Wildcards\n\n  The \"*\" character may be used as a wildcard. Suppose for example your library has debuggers named \"connect:bodyParser\", \"connect:compress\", \"connect:session\", instead of listing all three with `DEBUG=connect:bodyParser,connect.compress,connect:session`, you may simply do `DEBUG=connect:*`, or to run everything using this module simply use `DEBUG=*`.\n\n  You can also exclude specific debuggers by prefixing them with a \"-\" character.  For example, `DEBUG=* -connect:*` would include all debuggers except those starting with \"connect:\".\n\n## Browser support\n\n Debug works in the browser as well, currently persisted by `localStorage`. For example if you have `worker:a` and `worker:b` as shown below, and wish to debug both type `debug.enable('worker:*')` in the console and refresh the page, this will remain until you disable with `debug.disable()`. \n\n```js\na = debug('worker:a');\nb = debug('worker:b');\n\nsetInterval(function(){\n  a('doing some work');\n}, 1000);\n\nsetInterval(function(){\n  a('doing some work');\n}, 1200);\n```\n\n## License \n\n(The MIT License)\n\nCopyright (c) 2011 TJ Holowaychuk &lt;tj@vision-media.ca&gt;\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n'Software'), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.",
+  "readme": "# debug\n\n  tiny node.js debugging utility modelled after node core's debugging technique.\n\n## Installation\n\n```\n$ npm install debug\n```\n\n## Usage\n\n With `debug` you simply invoke the exported function to generate your debug function, passing it a name which will determine if a noop function is returned, or a decorated `console.error`, so all of the `console` format string goodies you're used to work fine. A unique color is selected per-function for visibility.\n \nExample _app.js_:\n\n```js\nvar debug = require('debug')('http')\n  , http = require('http')\n  , name = 'My App';\n\n// fake app\n\ndebug('booting %s', name);\n\nhttp.createServer(function(req, res){\n  debug(req.method + ' ' + req.url);\n  res.end('hello\\n');\n}).listen(3000, function(){\n  debug('listening');\n});\n\n// fake worker of some kind\n\nrequire('./worker');\n```\n\nExample _worker.js_:\n\n```js\nvar debug = require('debug')('worker');\n\nsetInterval(function(){\n  debug('doing some work');\n}, 1000);\n```\n\n The __DEBUG__ environment variable is then used to enable these based on space or comma-delimited names. Here are some examples:\n\n  ![debug http and worker](http://f.cl.ly/items/18471z1H402O24072r1J/Screenshot.png)\n\n  ![debug worker](http://f.cl.ly/items/1X413v1a3M0d3C2c1E0i/Screenshot.png)\n\n## Millisecond diff\n\n  When actively developing an application it can be useful to see when the time spent between one `debug()` call and the next. Suppose for example you invoke `debug()` before requesting a resource, and after as well, the \"+NNNms\" will show you how much time was spent between calls.\n\n  ![](http://f.cl.ly/items/2i3h1d3t121M2Z1A3Q0N/Screenshot.png)\n\n  When stderr is not a TTY, `Date#toUTCString()` is used, making it more useful for logging the debug information as shown below:\n  _(NOTE: Debug now uses stderr instead of stdout, so the correct shell command for this example is actually `DEBUG=* node example/worker 2> out &`)_\n  \n  ![](http://f.cl.ly/items/112H3i0e0o0P0a2Q2r11/Screenshot.png)\n  \n## Conventions\n\n If you're using this in one or more of your libraries, you _should_ use the name of your library so that developers may toggle debugging as desired without guessing names. If you have more than one debuggers you _should_ prefix them with your library name and use \":\" to separate features. For example \"bodyParser\" from Connect would then be \"connect:bodyParser\". \n\n## Wildcards\n\n  The \"*\" character may be used as a wildcard. Suppose for example your library has debuggers named \"connect:bodyParser\", \"connect:compress\", \"connect:session\", instead of listing all three with `DEBUG=connect:bodyParser,connect.compress,connect:session`, you may simply do `DEBUG=connect:*`, or to run everything using this module simply use `DEBUG=*`.\n\n  You can also exclude specific debuggers by prefixing them with a \"-\" character.  For example, `DEBUG=* -connect:*` would include all debuggers except those starting with \"connect:\".\n\n## Browser support\n\n Debug works in the browser as well, currently persisted by `localStorage`. For example if you have `worker:a` and `worker:b` as shown below, and wish to debug both type `debug.enable('worker:*')` in the console and refresh the page, this will remain until you disable with `debug.disable()`. \n\n```js\na = debug('worker:a');\nb = debug('worker:b');\n\nsetInterval(function(){\n  a('doing some work');\n}, 1000);\n\nsetInterval(function(){\n  a('doing some work');\n}, 1200);\n```\n\n## License \n\n(The MIT License)\n\nCopyright (c) 2011 TJ Holowaychuk &lt;tj@vision-media.ca&gt;\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n'Software'), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n",
   "readmeFilename": "Readme.md",
   "bugs": {
     "url": "https://github.com/visionmedia/debug/issues"
   },
-  "_id": "debug@0.7.2",
-  "_from": "debug@*",
-  "scripts": {}
+  "homepage": "https://github.com/visionmedia/debug",
+  "_id": "debug@0.7.4",
+  "_from": "debug@*"
 }
Index: Dev/trunk/src/node_modules/express/node_modules/fresh/package.json
===================================================================
--- Dev/trunk/src/node_modules/express/node_modules/fresh/package.json	(revision 486)
+++ Dev/trunk/src/node_modules/express/node_modules/fresh/package.json	(revision 487)
@@ -17,4 +17,8 @@
   "readmeFilename": "Readme.md",
   "_id": "fresh@0.1.0",
-  "_from": "fresh@0.1.0"
+  "dist": {
+    "shasum": "97ce63fda273b033f866d3a29b9920d034aa2074"
+  },
+  "_from": "fresh@0.1.0",
+  "_resolved": "https://registry.npmjs.org/fresh/-/fresh-0.1.0.tgz"
 }
Index: Dev/trunk/src/node_modules/express/node_modules/methods/package.json
===================================================================
--- Dev/trunk/src/node_modules/express/node_modules/methods/package.json	(revision 486)
+++ Dev/trunk/src/node_modules/express/node_modules/methods/package.json	(revision 487)
@@ -17,4 +17,8 @@
   "readme": "ERROR: No README data found!",
   "_id": "methods@0.0.1",
-  "_from": "methods@0.0.1"
+  "dist": {
+    "shasum": "c0a484b3e1f28764c5cfd234e1a156d47092ecca"
+  },
+  "_from": "methods@0.0.1",
+  "_resolved": "https://registry.npmjs.org/methods/-/methods-0.0.1.tgz"
 }
Index: Dev/trunk/src/node_modules/express/node_modules/mkdirp/package.json
===================================================================
--- Dev/trunk/src/node_modules/express/node_modules/mkdirp/package.json	(revision 486)
+++ Dev/trunk/src/node_modules/express/node_modules/mkdirp/package.json	(revision 487)
@@ -32,5 +32,10 @@
     "url": "https://github.com/substack/node-mkdirp/issues"
   },
+  "homepage": "https://github.com/substack/node-mkdirp",
   "_id": "mkdirp@0.3.4",
-  "_from": "mkdirp@0.3.4"
+  "dist": {
+    "shasum": "8642eb45b73f5cd19824e94b572871013af8a305"
+  },
+  "_from": "mkdirp@0.3.4",
+  "_resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.4.tgz"
 }
Index: Dev/trunk/src/node_modules/express/node_modules/range-parser/package.json
===================================================================
--- Dev/trunk/src/node_modules/express/node_modules/range-parser/package.json	(revision 486)
+++ Dev/trunk/src/node_modules/express/node_modules/range-parser/package.json	(revision 487)
@@ -17,4 +17,8 @@
   "readmeFilename": "Readme.md",
   "_id": "range-parser@0.0.4",
-  "_from": "range-parser@0.0.4"
+  "dist": {
+    "shasum": "a91c03c737187765f0dcdbe32fa824e52b47a9a6"
+  },
+  "_from": "range-parser@0.0.4",
+  "_resolved": "https://registry.npmjs.org/range-parser/-/range-parser-0.0.4.tgz"
 }
Index: Dev/trunk/src/node_modules/express/node_modules/send/node_modules/mime/package.json
===================================================================
--- Dev/trunk/src/node_modules/express/node_modules/send/node_modules/mime/package.json	(revision 486)
+++ Dev/trunk/src/node_modules/express/node_modules/send/node_modules/mime/package.json	(revision 487)
@@ -31,5 +31,10 @@
     "url": "https://github.com/broofa/node-mime/issues"
   },
+  "homepage": "https://github.com/broofa/node-mime",
   "_id": "mime@1.2.6",
-  "_from": "mime@1.2.6"
+  "dist": {
+    "shasum": "193c6817fabeefd6a5f29acabd0b6cebc2c0910f"
+  },
+  "_from": "mime@1.2.6",
+  "_resolved": "https://registry.npmjs.org/mime/-/mime-1.2.6.tgz"
 }
Index: Dev/trunk/src/node_modules/express/node_modules/send/package.json
===================================================================
--- Dev/trunk/src/node_modules/express/node_modules/send/package.json	(revision 486)
+++ Dev/trunk/src/node_modules/express/node_modules/send/package.json	(revision 487)
@@ -31,4 +31,8 @@
   "readmeFilename": "Readme.md",
   "_id": "send@0.1.0",
-  "_from": "send@0.1.0"
+  "dist": {
+    "shasum": "4ea2b3aca167b22699fdbe4eb5bce35fc57ca7d5"
+  },
+  "_from": "send@0.1.0",
+  "_resolved": "https://registry.npmjs.org/send/-/send-0.1.0.tgz"
 }
Index: Dev/trunk/src/node_modules/express/package.json
===================================================================
--- Dev/trunk/src/node_modules/express/package.json	(revision 486)
+++ Dev/trunk/src/node_modules/express/package.json	(revision 487)
@@ -80,5 +80,10 @@
     "url": "https://github.com/visionmedia/express/issues"
   },
+  "homepage": "https://github.com/visionmedia/express",
   "_id": "express@3.2.6",
-  "_from": "express@~3.2.3"
+  "dist": {
+    "shasum": "4e16430ceaf29ac1f185c6996d50ca6394ec6357"
+  },
+  "_from": "express@3.2.6",
+  "_resolved": "https://registry.npmjs.org/express/-/express-3.2.6.tgz"
 }
Index: Dev/trunk/src/node_modules/tv4/README.md
===================================================================
--- Dev/trunk/src/node_modules/tv4/README.md	(revision 486)
+++ Dev/trunk/src/node_modules/tv4/README.md	(revision 487)
@@ -85,5 +85,5 @@
 ## Cyclical JavaScript objects
 
-While they don't occur in proper JSON, JavaScript does support self-referencing objects. Any of the above calls support an optional final argument, checkRecursive. If true, tv4 will handle self-referencing objects properly - this slows down validation slightly, but that's better than a hanging script.
+While they don't occur in proper JSON, JavaScript does support self-referencing objects. Any of the above calls support an optional third argument: `checkRecursive`. If true, tv4 will handle self-referencing objects properly - this slows down validation slightly, but that's better than a hanging script.
 
 Consider this data, notice how both `a` and `b` refer to each other:
@@ -99,16 +99,26 @@
 ```
 
-If the final checkRecursive argument were missing, this would throw a "too much recursion" error. 
-
-To enable supprot for this pass `true` as additional argument to any of the regular validation methods: 
+If the `checkRecursive` argument were missing, this would throw a "too much recursion" error. 
+
+To enable support for this, pass `true` as additional argument to any of the regular validation methods: 
 
 ```javascript
 tv4.validate(a, aSchema, true);
-tv4.validate(a, schema, asynchronousFunction, true);
-
 tv4.validateResult(data, aSchema, true); 
 tv4.validateMultiple(data, aSchema, true);
 ```
 
+## The `banUnknownProperties` flag
+
+Sometimes, it is desirable to flag all unknown properties as an error.  This is especially useful during development, to catch typos and the like, even when extra custom-defined properties are allowed.
+
+As such, tv4 implements ["ban unknown properties" mode](https://github.com/json-schema/json-schema/wiki/ban-unknown-properties-mode-\(v5-proposal\)), enabled by a fourth-argument flag:
+
+```javascript
+tv4.validate(data, schema, checkRecursive, true);
+tv4.validateResult(data, schema, checkRecursive, true);
+tv4.validateMultiple(data, schema, checkRecursive, true);
+```
+
 ## API
 
@@ -229,5 +239,5 @@
 ##### addFormat(format, validationFunction)
 
-Add a custom format validator.
+Add a custom format validator. (There are no built-in format validators.)
 
 * `format` is a string, corresponding to the `"format"` value in schemas.
Index: Dev/trunk/src/node_modules/tv4/package.json
===================================================================
--- Dev/trunk/src/node_modules/tv4/package.json	(revision 486)
+++ Dev/trunk/src/node_modules/tv4/package.json	(revision 487)
@@ -1,5 +1,5 @@
 {
   "name": "tv4",
-  "version": "1.0.11",
+  "version": "1.0.16",
   "author": {
     "name": "Geraint Luff"
@@ -51,5 +51,6 @@
     "grunt-markdown": "~0.3.0",
     "grunt-component": "~0.1.4",
-    "grunt-push-release": "~0.1.1"
+    "grunt-push-release": "~0.1.1",
+    "grunt-regex-replace": "~0.2.5"
   },
   "engines": {
@@ -57,7 +58,8 @@
   },
   "scripts": {
-    "test": "grunt test"
+    "test": "grunt test",
+    "prepublish": "grunt prepublish"
   },
-  "readme": "# Tiny Validator (for v4 JSON Schema)\n\n[![Build Status](https://secure.travis-ci.org/geraintluff/tv4.png?branch=master)](http://travis-ci.org/geraintluff/tv4) [![Dependency Status](https://gemnasium.com/geraintluff/tv4.png)](https://gemnasium.com/geraintluff/tv4) [![NPM version](https://badge.fury.io/js/tv4.png)](http://badge.fury.io/js/tv4)\n\nUse [json-schema](http://json-schema.org/) [draft v4](http://json-schema.org/latest/json-schema-core.html) to validate simple values and complex objects using a rich [validation vocabulary](http://json-schema.org/latest/json-schema-validation.html) ([examples](http://json-schema.org/examples.html)).\n\nThere is support for `$ref` with JSON Pointer fragment paths (```other-schema.json#/properties/myKey```).\n\n## Usage 1: Simple validation\n\n```javascript\nvar valid = tv4.validate(data, schema);\n```\n\nIf validation returns ```false```, then an explanation of why validation failed can be found in ```tv4.error```.\n\nThe error object will look something like:\n```json\n{\n    \"code\": 0,\n    \"message\": \"Invalid type: string\",\n    \"dataPath\": \"/intKey\",\n    \"schemaKey\": \"/properties/intKey/type\"\n}\n```\n\nThe `\"code\"` property will refer to one of the values in `tv4.errorCodes` - in this case, `tv4.errorCodes.INVALID_TYPE`.\n\nTo enable external schema to be referenced, you use:\n```javascript\ntv4.addSchema(url, schema);\n```\n\nIf schemas are referenced (```$ref```) but not known, then validation will return ```true``` and the missing schema(s) will be listed in ```tv4.missing```. For more info see the API documentation below.\n\n## Usage 2: Multi-threaded validation\n\nStoring the error and missing schemas does not work well in multi-threaded environments, so there is an alternative syntax:\n\n```javascript\nvar result = tv4.validateResult(data, schema);\n```\n\nThe result will look something like:\n```json\n{\n    \"valid\": false,\n    \"error\": {...},\n    \"missing\": [...]\n}\n```\n\n## Usage 3: Multiple errors\n\nNormally, `tv4` stops when it encounters the first validation error.  However, you can collect an array of validation errors using:\n\n```javascript\nvar result = tv4.validateMultiple(data, schema);\n```\n\nThe result will look something like:\n```json\n{\n    \"valid\": false,\n    \"errors\": [\n        {...},\n        ...\n    ],\n    \"missing\": [...]\n}\n```\n\n## Asynchronous validation\n\nSupport for asynchronous validation (where missing schemas are fetched) can be added by including an extra JavaScript file.  Currently, the only version requires jQuery (`tv4.async-jquery.js`), but the code is very short and should be fairly easy to modify for other libraries (such as MooTools).\n\nUsage:\n\n```javascript\ntv4.validate(data, schema, function (isValid, validationError) { ... });\n```\n\n`validationFailure` is simply taken from `tv4.error`.\n\n## Cyclical JavaScript objects\n\nWhile they don't occur in proper JSON, JavaScript does support self-referencing objects. Any of the above calls support an optional final argument, checkRecursive. If true, tv4 will handle self-referencing objects properly - this slows down validation slightly, but that's better than a hanging script.\n\nConsider this data, notice how both `a` and `b` refer to each other:\n\n```javascript\nvar a = {};\nvar b = { a: a };\na.b = b;\nvar aSchema = { properties: { b: { $ref: 'bSchema' }}};\nvar bSchema = { properties: { a: { $ref: 'aSchema' }}};\ntv4.addSchema('aSchema', aSchema);\ntv4.addSchema('bSchema', bSchema);\n```\n\nIf the final checkRecursive argument were missing, this would throw a \"too much recursion\" error. \n\nTo enable supprot for this pass `true` as additional argument to any of the regular validation methods: \n\n```javascript\ntv4.validate(a, aSchema, true);\ntv4.validate(a, schema, asynchronousFunction, true);\n\ntv4.validateResult(data, aSchema, true); \ntv4.validateMultiple(data, aSchema, true);\n```\n\n## API\n\nThere are additional api commands available for more complex use-cases:\n\n##### addSchema(uri, schema)\nPre-register a schema for reference by other schema and synchronous validation.\n\n````js\ntv4.addSchema('http://example.com/schema', { ... });\n````\n\n* `uri` the uri to identify this schema.\n* `schema` the schema object.\n\nSchemas that have their `id` property set can be added directly.\n\n````js\ntv4.addSchema({ ... });\n````\n\n##### getSchema(uri)\n\nReturn a schema from the cache.\n\n* `uri` the uri of the schema (may contain a `#` fragment)\n\n````js\nvar schema = tv4.getSchema('http://example.com/schema');\n````\n\n##### getSchemaMap()\n\nReturn a shallow copy of the schema cache, mapping schema document URIs to schema objects.\n\n````\nvar map = tv4.getSchemaMap();\n\nvar schema = map[uri];\n````\n\n##### getSchemaUris(filter)\n\nReturn an Array with known schema document URIs.\n\n* `filter` optional RegExp to filter URIs\n\n````\nvar arr = tv4.getSchemaUris();\n\n// optional filter using a RegExp\nvar arr = tv4.getSchemaUris(/^https?://example.com/);\n````\n\n##### getMissingUris(filter)\n\nReturn an Array with schema document URIs that are used as `$ref` in known schemas but which currently have no associated schema data.\n\nUse this in combination with `tv4.addSchema(uri, schema)` to preload the cache for complete synchronous validation with.\n\n* `filter` optional RegExp to filter URIs\n\n````\nvar arr = tv4.getMissingUris();\n\n// optional filter using a RegExp\nvar arr = tv4.getMissingUris(/^https?://example.com/);\n````\n\n##### dropSchemas()\n\nDrop all known schema document URIs from the cache.\n\n````\ntv4.dropSchemas();\n````\n\n##### freshApi()\n\nReturn a new tv4 instance with no shared state.\n\n````\nvar otherTV4 = tv4.freshApi();\n````\n\n##### reset()\n\nManually reset validation status from the simple `tv4.validate(data, schema)`. Although tv4 will self reset on each validation there are some implementation scenarios where this is useful.\n\n````\ntv4.reset();\n````\n\n##### language(code)\n\nSelect the language map used for reporting.\n\n* `code` is a language code, like `'en'` or `'en-gb'`\n\n````\ntv4.language('en-gb');\n````\n\n##### addLanguage(code, map)\n\nAdd a new language map for selection by `tv4.language(code)`\n\n* `code` is new language code\n* `map` is an object mapping error IDs or constant names (e.g. `103` or `\"NUMBER_MAXIMUM\"`) to language strings.\n\n````\ntv4.addLanguage('fr', { ... });\n\n// select for use\ntv4.language('fr')\n````\n\n##### addFormat(format, validationFunction)\n\nAdd a custom format validator.\n\n* `format` is a string, corresponding to the `\"format\"` value in schemas.\n* `validationFunction` is a function that either returns:\n  * `null` (meaning no error)\n  * an error string (explaining the reason for failure)\n\n````\ntv4.addFormat('decimal-digits', function (data, schema) {\n\tif (typeof data === 'string' && !/^[0-9]+$/.test(data)) {\n\t\treturn null;\n\t}\n\treturn \"must be string of decimal digits\";\n});\n````\n\nAlternatively, multiple formats can be added at the same time using an object:\n````\ntv4.addFormat({\n\t'my-format': function () {...},\n\t'other-format': function () {...}\n});\n````\n\n## Demos\n\n### Basic usage\n<div class=\"content inline-demo\" markdown=\"1\" data-demo=\"demo1\">\n<pre class=\"code\" id=\"demo1\">\nvar schema = {\n\t\"items\": {\n\t\t\"type\": \"boolean\"\n\t}\n};\nvar data1 = [true, false];\nvar data2 = [true, 123];\n\nalert(\"data 1: \" + tv4.validate(data1, schema)); // true\nalert(\"data 2: \" + tv4.validate(data2, schema)); // false\nalert(\"data 2 error: \" + JSON.stringify(tv4.error, null, 4));\n</pre>\n</div>\n\n### Use of <code>$ref</code>\n<div class=\"content inline-demo\" markdown=\"1\" data-demo=\"demo2\">\n<pre class=\"code\" id=\"demo2\">\nvar schema = {\n\t\"type\": \"array\",\n\t\"items\": {\"$ref\": \"#\"}\n};\nvar data1 = [[], [[]]];\nvar data2 = [[], [true, []]];\n\nalert(\"data 1: \" + tv4.validate(data1, schema)); // true\nalert(\"data 2: \" + tv4.validate(data2, schema)); // false\n</pre>\n</div>\n\n### Missing schema\n<div class=\"content inline-demo\" markdown=\"1\" data-demo=\"demo3\">\n<pre class=\"code\" id=\"demo3\">\nvar schema = {\n\t\"type\": \"array\",\n\t\"items\": {\"$ref\": \"http://example.com/schema\" }\n};\nvar data = [1, 2, 3];\n\nalert(\"Valid: \" + tv4.validate(data, schema)); // true\nalert(\"Missing schemas: \" + JSON.stringify(tv4.missing));\n</pre>\n</div>\n\n### Referencing remote schema\n<div class=\"content inline-demo\" markdown=\"1\" data-demo=\"demo4\">\n<pre class=\"code\" id=\"demo4\">\ntv4.addSchema(\"http://example.com/schema\", {\n\t\"definitions\": {\n\t\t\"arrayItem\": {\"type\": \"boolean\"}\n\t}\n});\nvar schema = {\n\t\"type\": \"array\",\n\t\"items\": {\"$ref\": \"http://example.com/schema#/definitions/arrayItem\" }\n};\nvar data1 = [true, false, true];\nvar data2 = [1, 2, 3];\n\nalert(\"data 1: \" + tv4.validate(data1, schema)); // true\nalert(\"data 2: \" + tv4.validate(data2, schema)); // false\n</pre>\n</div>\n\n## Supported platforms\n\n* Node.js\n* All modern browsers\n* IE >= 7\n\n## Installation\n\nYou can manually download [`tv4.js`](https://raw.github.com/geraintluff/tv4/master/tv4.js) or the minified [`tv4.min.js`](https://raw.github.com/geraintluff/tv4/master/tv4.min.js) and include it in your html to create the global `tv4` variable.\n\nAlternately use it as a CommonJS module:\n\n````js\nvar tv4 = require('tv4');\n````\n\n#### npm\n\n````\n$ npm install tv4\n````\n\n#### bower\n\n````\n$ bower install tv4\n````\n\n#### component.io\n\n````\n$ component install geraintluff/tv4\n````\n\n## Build and test\n\nYou can rebuild and run the node and browser tests using node.js and [grunt](http://http://gruntjs.com/):\n\nMake sure you have the global grunt cli command:\n````\n$ npm install grunt-cli -g\n````\n\nClone the git repos, open a shell in the root folder and install the development dependencies:\n\n````\n$ npm install\n````\n\nRebuild and run the tests:\n````\n$ grunt\n````\n\nIt will run a build and display one Spec-style report for the node.js and two Dot-style reports for both the plain and minified browser tests (via phantomJS). You can also use your own browser to manually run the suites by opening [`test/index.html`](http://geraintluff.github.io/tv4/test/index.html) and [`test/index-min.html`](http://geraintluff.github.io/tv4/test/index-min.html).\n\n## Contributing\n\nPull-requests for fixes and expansions are welcome. Edit the partial files in `/source` and add your tests in a suitable suite or folder under `/test/tests` and run `grunt` to rebuild and run the test suite. Try to maintain an idiomatic coding style and add tests for any new features. It is recommend to discuss big changes in an Issue.\n\n## Packages using tv4\n\n* [chai-json-schema](http://chaijs.com/plugins/chai-json-schema) is a [Chai Assertion Library](http://chaijs.com) plugin to assert values against json-schema.\n* [grunt-tv4](http://www.github.com/Bartvds/grunt-tv4) is a plugin for [Grunt](http://http://gruntjs.com/) that uses tv4 to bulk validate json files.\n\n## License\n\nThe code is available as \"public domain\", meaning that it is completely free to use, without any restrictions at all.  Read the full license [here](http://geraintluff.github.com/tv4/LICENSE.txt).\n\nIt's also available under an [MIT license](http://jsonary.com/LICENSE.txt).\n",
+  "readme": "# Tiny Validator (for v4 JSON Schema)\n\n[![Build Status](https://secure.travis-ci.org/geraintluff/tv4.png?branch=master)](http://travis-ci.org/geraintluff/tv4) [![Dependency Status](https://gemnasium.com/geraintluff/tv4.png)](https://gemnasium.com/geraintluff/tv4) [![NPM version](https://badge.fury.io/js/tv4.png)](http://badge.fury.io/js/tv4)\n\nUse [json-schema](http://json-schema.org/) [draft v4](http://json-schema.org/latest/json-schema-core.html) to validate simple values and complex objects using a rich [validation vocabulary](http://json-schema.org/latest/json-schema-validation.html) ([examples](http://json-schema.org/examples.html)).\n\nThere is support for `$ref` with JSON Pointer fragment paths (```other-schema.json#/properties/myKey```).\n\n## Usage 1: Simple validation\n\n```javascript\nvar valid = tv4.validate(data, schema);\n```\n\nIf validation returns ```false```, then an explanation of why validation failed can be found in ```tv4.error```.\n\nThe error object will look something like:\n```json\n{\n    \"code\": 0,\n    \"message\": \"Invalid type: string\",\n    \"dataPath\": \"/intKey\",\n    \"schemaKey\": \"/properties/intKey/type\"\n}\n```\n\nThe `\"code\"` property will refer to one of the values in `tv4.errorCodes` - in this case, `tv4.errorCodes.INVALID_TYPE`.\n\nTo enable external schema to be referenced, you use:\n```javascript\ntv4.addSchema(url, schema);\n```\n\nIf schemas are referenced (```$ref```) but not known, then validation will return ```true``` and the missing schema(s) will be listed in ```tv4.missing```. For more info see the API documentation below.\n\n## Usage 2: Multi-threaded validation\n\nStoring the error and missing schemas does not work well in multi-threaded environments, so there is an alternative syntax:\n\n```javascript\nvar result = tv4.validateResult(data, schema);\n```\n\nThe result will look something like:\n```json\n{\n    \"valid\": false,\n    \"error\": {...},\n    \"missing\": [...]\n}\n```\n\n## Usage 3: Multiple errors\n\nNormally, `tv4` stops when it encounters the first validation error.  However, you can collect an array of validation errors using:\n\n```javascript\nvar result = tv4.validateMultiple(data, schema);\n```\n\nThe result will look something like:\n```json\n{\n    \"valid\": false,\n    \"errors\": [\n        {...},\n        ...\n    ],\n    \"missing\": [...]\n}\n```\n\n## Asynchronous validation\n\nSupport for asynchronous validation (where missing schemas are fetched) can be added by including an extra JavaScript file.  Currently, the only version requires jQuery (`tv4.async-jquery.js`), but the code is very short and should be fairly easy to modify for other libraries (such as MooTools).\n\nUsage:\n\n```javascript\ntv4.validate(data, schema, function (isValid, validationError) { ... });\n```\n\n`validationFailure` is simply taken from `tv4.error`.\n\n## Cyclical JavaScript objects\n\nWhile they don't occur in proper JSON, JavaScript does support self-referencing objects. Any of the above calls support an optional third argument: `checkRecursive`. If true, tv4 will handle self-referencing objects properly - this slows down validation slightly, but that's better than a hanging script.\n\nConsider this data, notice how both `a` and `b` refer to each other:\n\n```javascript\nvar a = {};\nvar b = { a: a };\na.b = b;\nvar aSchema = { properties: { b: { $ref: 'bSchema' }}};\nvar bSchema = { properties: { a: { $ref: 'aSchema' }}};\ntv4.addSchema('aSchema', aSchema);\ntv4.addSchema('bSchema', bSchema);\n```\n\nIf the `checkRecursive` argument were missing, this would throw a \"too much recursion\" error. \n\nTo enable support for this, pass `true` as additional argument to any of the regular validation methods: \n\n```javascript\ntv4.validate(a, aSchema, true);\ntv4.validateResult(data, aSchema, true); \ntv4.validateMultiple(data, aSchema, true);\n```\n\n## The `banUnknownProperties` flag\n\nSometimes, it is desirable to flag all unknown properties as an error.  This is especially useful during development, to catch typos and the like, even when extra custom-defined properties are allowed.\n\nAs such, tv4 implements [\"ban unknown properties\" mode](https://github.com/json-schema/json-schema/wiki/ban-unknown-properties-mode-\\(v5-proposal\\)), enabled by a fourth-argument flag:\n\n```javascript\ntv4.validate(data, schema, checkRecursive, true);\ntv4.validateResult(data, schema, checkRecursive, true);\ntv4.validateMultiple(data, schema, checkRecursive, true);\n```\n\n## API\n\nThere are additional api commands available for more complex use-cases:\n\n##### addSchema(uri, schema)\nPre-register a schema for reference by other schema and synchronous validation.\n\n````js\ntv4.addSchema('http://example.com/schema', { ... });\n````\n\n* `uri` the uri to identify this schema.\n* `schema` the schema object.\n\nSchemas that have their `id` property set can be added directly.\n\n````js\ntv4.addSchema({ ... });\n````\n\n##### getSchema(uri)\n\nReturn a schema from the cache.\n\n* `uri` the uri of the schema (may contain a `#` fragment)\n\n````js\nvar schema = tv4.getSchema('http://example.com/schema');\n````\n\n##### getSchemaMap()\n\nReturn a shallow copy of the schema cache, mapping schema document URIs to schema objects.\n\n````\nvar map = tv4.getSchemaMap();\n\nvar schema = map[uri];\n````\n\n##### getSchemaUris(filter)\n\nReturn an Array with known schema document URIs.\n\n* `filter` optional RegExp to filter URIs\n\n````\nvar arr = tv4.getSchemaUris();\n\n// optional filter using a RegExp\nvar arr = tv4.getSchemaUris(/^https?://example.com/);\n````\n\n##### getMissingUris(filter)\n\nReturn an Array with schema document URIs that are used as `$ref` in known schemas but which currently have no associated schema data.\n\nUse this in combination with `tv4.addSchema(uri, schema)` to preload the cache for complete synchronous validation with.\n\n* `filter` optional RegExp to filter URIs\n\n````\nvar arr = tv4.getMissingUris();\n\n// optional filter using a RegExp\nvar arr = tv4.getMissingUris(/^https?://example.com/);\n````\n\n##### dropSchemas()\n\nDrop all known schema document URIs from the cache.\n\n````\ntv4.dropSchemas();\n````\n\n##### freshApi()\n\nReturn a new tv4 instance with no shared state.\n\n````\nvar otherTV4 = tv4.freshApi();\n````\n\n##### reset()\n\nManually reset validation status from the simple `tv4.validate(data, schema)`. Although tv4 will self reset on each validation there are some implementation scenarios where this is useful.\n\n````\ntv4.reset();\n````\n\n##### language(code)\n\nSelect the language map used for reporting.\n\n* `code` is a language code, like `'en'` or `'en-gb'`\n\n````\ntv4.language('en-gb');\n````\n\n##### addLanguage(code, map)\n\nAdd a new language map for selection by `tv4.language(code)`\n\n* `code` is new language code\n* `map` is an object mapping error IDs or constant names (e.g. `103` or `\"NUMBER_MAXIMUM\"`) to language strings.\n\n````\ntv4.addLanguage('fr', { ... });\n\n// select for use\ntv4.language('fr')\n````\n\n##### addFormat(format, validationFunction)\n\nAdd a custom format validator. (There are no built-in format validators.)\n\n* `format` is a string, corresponding to the `\"format\"` value in schemas.\n* `validationFunction` is a function that either returns:\n  * `null` (meaning no error)\n  * an error string (explaining the reason for failure)\n\n````\ntv4.addFormat('decimal-digits', function (data, schema) {\n\tif (typeof data === 'string' && !/^[0-9]+$/.test(data)) {\n\t\treturn null;\n\t}\n\treturn \"must be string of decimal digits\";\n});\n````\n\nAlternatively, multiple formats can be added at the same time using an object:\n````\ntv4.addFormat({\n\t'my-format': function () {...},\n\t'other-format': function () {...}\n});\n````\n\n## Demos\n\n### Basic usage\n<div class=\"content inline-demo\" markdown=\"1\" data-demo=\"demo1\">\n<pre class=\"code\" id=\"demo1\">\nvar schema = {\n\t\"items\": {\n\t\t\"type\": \"boolean\"\n\t}\n};\nvar data1 = [true, false];\nvar data2 = [true, 123];\n\nalert(\"data 1: \" + tv4.validate(data1, schema)); // true\nalert(\"data 2: \" + tv4.validate(data2, schema)); // false\nalert(\"data 2 error: \" + JSON.stringify(tv4.error, null, 4));\n</pre>\n</div>\n\n### Use of <code>$ref</code>\n<div class=\"content inline-demo\" markdown=\"1\" data-demo=\"demo2\">\n<pre class=\"code\" id=\"demo2\">\nvar schema = {\n\t\"type\": \"array\",\n\t\"items\": {\"$ref\": \"#\"}\n};\nvar data1 = [[], [[]]];\nvar data2 = [[], [true, []]];\n\nalert(\"data 1: \" + tv4.validate(data1, schema)); // true\nalert(\"data 2: \" + tv4.validate(data2, schema)); // false\n</pre>\n</div>\n\n### Missing schema\n<div class=\"content inline-demo\" markdown=\"1\" data-demo=\"demo3\">\n<pre class=\"code\" id=\"demo3\">\nvar schema = {\n\t\"type\": \"array\",\n\t\"items\": {\"$ref\": \"http://example.com/schema\" }\n};\nvar data = [1, 2, 3];\n\nalert(\"Valid: \" + tv4.validate(data, schema)); // true\nalert(\"Missing schemas: \" + JSON.stringify(tv4.missing));\n</pre>\n</div>\n\n### Referencing remote schema\n<div class=\"content inline-demo\" markdown=\"1\" data-demo=\"demo4\">\n<pre class=\"code\" id=\"demo4\">\ntv4.addSchema(\"http://example.com/schema\", {\n\t\"definitions\": {\n\t\t\"arrayItem\": {\"type\": \"boolean\"}\n\t}\n});\nvar schema = {\n\t\"type\": \"array\",\n\t\"items\": {\"$ref\": \"http://example.com/schema#/definitions/arrayItem\" }\n};\nvar data1 = [true, false, true];\nvar data2 = [1, 2, 3];\n\nalert(\"data 1: \" + tv4.validate(data1, schema)); // true\nalert(\"data 2: \" + tv4.validate(data2, schema)); // false\n</pre>\n</div>\n\n## Supported platforms\n\n* Node.js\n* All modern browsers\n* IE >= 7\n\n## Installation\n\nYou can manually download [`tv4.js`](https://raw.github.com/geraintluff/tv4/master/tv4.js) or the minified [`tv4.min.js`](https://raw.github.com/geraintluff/tv4/master/tv4.min.js) and include it in your html to create the global `tv4` variable.\n\nAlternately use it as a CommonJS module:\n\n````js\nvar tv4 = require('tv4');\n````\n\n#### npm\n\n````\n$ npm install tv4\n````\n\n#### bower\n\n````\n$ bower install tv4\n````\n\n#### component.io\n\n````\n$ component install geraintluff/tv4\n````\n\n## Build and test\n\nYou can rebuild and run the node and browser tests using node.js and [grunt](http://http://gruntjs.com/):\n\nMake sure you have the global grunt cli command:\n````\n$ npm install grunt-cli -g\n````\n\nClone the git repos, open a shell in the root folder and install the development dependencies:\n\n````\n$ npm install\n````\n\nRebuild and run the tests:\n````\n$ grunt\n````\n\nIt will run a build and display one Spec-style report for the node.js and two Dot-style reports for both the plain and minified browser tests (via phantomJS). You can also use your own browser to manually run the suites by opening [`test/index.html`](http://geraintluff.github.io/tv4/test/index.html) and [`test/index-min.html`](http://geraintluff.github.io/tv4/test/index-min.html).\n\n## Contributing\n\nPull-requests for fixes and expansions are welcome. Edit the partial files in `/source` and add your tests in a suitable suite or folder under `/test/tests` and run `grunt` to rebuild and run the test suite. Try to maintain an idiomatic coding style and add tests for any new features. It is recommend to discuss big changes in an Issue.\n\n## Packages using tv4\n\n* [chai-json-schema](http://chaijs.com/plugins/chai-json-schema) is a [Chai Assertion Library](http://chaijs.com) plugin to assert values against json-schema.\n* [grunt-tv4](http://www.github.com/Bartvds/grunt-tv4) is a plugin for [Grunt](http://http://gruntjs.com/) that uses tv4 to bulk validate json files.\n\n## License\n\nThe code is available as \"public domain\", meaning that it is completely free to use, without any restrictions at all.  Read the full license [here](http://geraintluff.github.com/tv4/LICENSE.txt).\n\nIt's also available under an [MIT license](http://jsonary.com/LICENSE.txt).\n",
   "readmeFilename": "README.md",
   "bugs": {
@@ -65,5 +67,9 @@
   },
   "homepage": "https://github.com/geraintluff/tv4",
-  "_id": "tv4@1.0.11",
-  "_from": "tv4@"
+  "_id": "tv4@1.0.16",
+  "dist": {
+    "shasum": "f35372c01e94355b7aaff5860455fd231e2d9802"
+  },
+  "_from": "tv4@1.0.16",
+  "_resolved": "https://registry.npmjs.org/tv4/-/tv4-1.0.16.tgz"
 }
Index: Dev/trunk/src/node_modules/tv4/tv4.async-jquery.js
===================================================================
--- Dev/trunk/src/node_modules/tv4/tv4.async-jquery.js	(revision 486)
+++ Dev/trunk/src/node_modules/tv4/tv4.async-jquery.js	(revision 487)
@@ -4,14 +4,14 @@
 if (typeof (tv4.asyncValidate) === 'undefined') {
 	tv4.syncValidate = tv4.validate;
-	tv4.validate = function (data, schema, callback, checkRecursive) {
+	tv4.validate = function (data, schema, callback, checkRecursive, banUnknownProperties) {
 		if (typeof (callback) === 'undefined') {
-			return this.syncValidate(data, schema, checkRecursive);
+			return this.syncValidate(data, schema, checkRecursive, banUnknownProperties);
 		} else {
-			return this.asyncValidate(data, schema, callback, checkRecursive);
+			return this.asyncValidate(data, schema, callback, checkRecursive, banUnknownProperties);
 		}
 	};
-	tv4.asyncValidate = function (data, schema, callback, checkRecursive) {
+	tv4.asyncValidate = function (data, schema, callback, checkRecursive, banUnknownProperties) {
 		var $ = jQuery;
-		var result = tv4.validate(data, schema, checkRecursive);
+		var result = tv4.validate(data, schema, checkRecursive, banUnknownProperties);
 		if (!tv4.missing.length) {
 			callback(result, tv4.error);
@@ -28,5 +28,5 @@
 			// When all requests done, try again
 			$.when.apply($, missingSchemas).done(function () {
-				var result = tv4.asyncValidate(data, schema, callback, checkRecursive);
+				var result = tv4.asyncValidate(data, schema, callback, checkRecursive, banUnknownProperties);
 			});
 		}
Index: Dev/trunk/src/node_modules/tv4/tv4.js
===================================================================
--- Dev/trunk/src/node_modules/tv4/tv4.js	(revision 486)
+++ Dev/trunk/src/node_modules/tv4/tv4.js	(revision 487)
@@ -133,5 +133,7 @@
 		this.scannedFrozen = [];
 		this.scannedFrozenSchemas = [];
-		this.key = 'tv4_validation_id';
+		this.scannedFrozenValidationErrors = [];
+		this.validatedSchemasKey = 'tv4_validation_id';
+		this.validationErrorsKey = 'tv4_validation_errors_id';
 	}
 	if (trackUnknownProperties) {
@@ -240,12 +242,12 @@
 };
 ValidatorContext.prototype.searchSchemas = function (schema, url) {
-	if (typeof schema.id === "string") {
-		if (isTrustedUrl(url, schema.id)) {
-			if (this.schemas[schema.id] === undefined) {
-				this.schemas[schema.id] = schema;
-			}
-		}
-	}
-	if (typeof schema === "object") {
+	if (schema && typeof schema === "object") {
+		if (typeof schema.id === "string") {
+			if (isTrustedUrl(url, schema.id)) {
+				if (this.schemas[schema.id] === undefined) {
+					this.schemas[schema.id] = schema;
+				}
+			}
+		}
 		for (var key in schema) {
 			if (key !== "enum") {
@@ -264,5 +266,5 @@
 ValidatorContext.prototype.addSchema = function (url, schema) {
 	//overload
-	if (typeof schema === 'undefined') {
+	if (typeof url !== 'string' || typeof schema === 'undefined') {
 		if (typeof url === 'object' && typeof url.id === 'string') {
 			schema = url;
@@ -331,11 +333,24 @@
 	}
 
-	if (this.checkRecursive && (typeof data) === 'object') {
+	var startErrorCount = this.errors.length;
+	var frozenIndex, scannedFrozenSchemaIndex = null, scannedSchemasIndex = null;
+	if (this.checkRecursive && data && typeof data === 'object') {
 		topLevel = !this.scanned.length;
-		if (data[this.key] && data[this.key].indexOf(schema) !== -1) { return null; }
-		var frozenIndex;
+		if (data[this.validatedSchemasKey]) {
+			var schemaIndex = data[this.validatedSchemasKey].indexOf(schema);
+			if (schemaIndex !== -1) {
+				this.errors = this.errors.concat(data[this.validationErrorsKey][schemaIndex]);
+				return null;
+			}
+		}
 		if (Object.isFrozen(data)) {
 			frozenIndex = this.scannedFrozen.indexOf(data);
-			if (frozenIndex !== -1 && this.scannedFrozenSchemas[frozenIndex].indexOf(schema) !== -1) { return null; }
+			if (frozenIndex !== -1) {
+				var frozenSchemaIndex = this.scannedFrozenSchemas[frozenIndex].indexOf(schema);
+				if (frozenSchemaIndex !== -1) {
+					this.errors = this.errors.concat(this.scannedFrozenValidationErrors[frozenIndex][frozenSchemaIndex]);
+					return null;
+				}
+			}
 		}
 		this.scanned.push(data);
@@ -346,9 +361,15 @@
 				this.scannedFrozenSchemas.push([]);
 			}
-			this.scannedFrozenSchemas[frozenIndex].push(schema);
+			scannedFrozenSchemaIndex = this.scannedFrozenSchemas[frozenIndex].length;
+			this.scannedFrozenSchemas[frozenIndex][scannedFrozenSchemaIndex] = schema;
+			this.scannedFrozenValidationErrors[frozenIndex][scannedFrozenSchemaIndex] = [];
 		} else {
-			if (!data[this.key]) {
+			if (!data[this.validatedSchemasKey]) {
 				try {
-					Object.defineProperty(data, this.key, {
+					Object.defineProperty(data, this.validatedSchemasKey, {
+						value: [],
+						configurable: true
+					});
+					Object.defineProperty(data, this.validationErrorsKey, {
 						value: [],
 						configurable: true
@@ -356,8 +377,11 @@
 				} catch (e) {
 					//IE 7/8 workaround
-					data[this.key] = [];
-				}
-			}
-			data[this.key].push(schema);
+					data[this.validatedSchemasKey] = [];
+					data[this.validationErrorsKey] = [];
+				}
+			}
+			scannedSchemasIndex = data[this.validatedSchemasKey].length;
+			data[this.validatedSchemasKey][scannedSchemasIndex] = schema;
+			data[this.validationErrorsKey][scannedSchemasIndex] = [];
 		}
 	}
@@ -376,5 +400,5 @@
 		while (this.scanned.length) {
 			var item = this.scanned.pop();
-			delete item[this.key];
+			delete item[this.validatedSchemasKey];
 		}
 		this.scannedFrozen = [];
@@ -391,4 +415,10 @@
 			this.prefixErrors(errorCount, dataPart, schemaPart);
 		}
+	}
+	
+	if (scannedFrozenSchemaIndex !== null) {
+		this.scannedFrozenValidationErrors[frozenIndex][scannedFrozenSchemaIndex] = this.errors.slice(startErrorCount);
+	} else if (scannedSchemasIndex !== null) {
+		data[this.validationErrorsKey][scannedSchemasIndex] = this.errors.slice(startErrorCount);
 	}
 
@@ -996,11 +1026,11 @@
 }
 function normSchema(schema, baseUri) {
-	if (baseUri === undefined) {
-		baseUri = schema.id;
-	} else if (typeof schema.id === "string") {
-		baseUri = resolveUrl(baseUri, schema.id);
-		schema.id = baseUri;
-	}
-	if (typeof schema === "object") {
+	if (schema && typeof schema === "object") {
+		if (baseUri === undefined) {
+			baseUri = schema.id;
+		} else if (typeof schema.id === "string") {
+			baseUri = resolveUrl(baseUri, schema.id);
+			schema.id = baseUri;
+		}
 		if (Array.isArray(schema)) {
 			for (var i = 0; i < schema.length; i++) {
@@ -1050,5 +1080,5 @@
 	FORMAT_CUSTOM: 500,
 	// Schema structure
-	CIRCULAR_REFERENCE: 500,
+	CIRCULAR_REFERENCE: 600,
 	// Non-standard validation options
 	UNKNOWN_PROPERTY: 1000
@@ -1091,14 +1121,29 @@
 
 function ValidationError(code, message, dataPath, schemaPath, subErrors) {
+	Error.call(this);
 	if (code === undefined) {
 		throw new Error ("No code supplied for error: "+ message);
 	}
+	this.message = message;
 	this.code = code;
-	this.message = message;
 	this.dataPath = dataPath || "";
 	this.schemaPath = schemaPath || "";
 	this.subErrors = subErrors || null;
-}
-ValidationError.prototype = new Error();
+
+	var err = new Error(this.message);
+	this.stack = err.stack || err.stacktrace;
+	if (!this.stack) {
+		try {
+			throw err;
+		}
+		catch(err) {
+			this.stack = err.stack || err.stacktrace;
+		}
+	}
+}
+ValidationError.prototype = Object.create(Error.prototype);
+ValidationError.prototype.constructor = ValidationError;
+ValidationError.prototype.name = 'ValidationError';
+
 ValidationError.prototype.prefixWith = function (dataPrefix, schemaPrefix) {
 	if (dataPrefix !== null) {
@@ -1265,4 +1310,2 @@
 
 })(this);
-
-//@ sourceMappingURL=tv4.js.map
Index: Dev/trunk/src/package.json
===================================================================
--- Dev/trunk/src/package.json	(revision 486)
+++ Dev/trunk/src/package.json	(revision 487)
@@ -4,5 +4,5 @@
   "description": "QED Server",
   "dependencies": {
-    "express": "~3.2.3",
+    "express": "~3.2.6",
     "underscore": "~1.4.4",
     "passport": "~0.1.17",
@@ -11,5 +11,5 @@
     "request": "~2.21.0",
     "ya-csv": "~0.9.2",
-    "tv4": "~1.0.11"
+    "tv4": "~1.0.16"
   },
   "engines": {
Index: Dev/trunk/src/server/app.js
===================================================================
--- Dev/trunk/src/server/app.js	(revision 486)
+++ Dev/trunk/src/server/app.js	(revision 487)
@@ -9,4 +9,7 @@
   , _ = require("underscore")
   , tv4 = require("tv4")
+  , HTTPResult = require("./util/http-result")
+  , etags = require("./util/etags")
+  , cryptoken = require('./util/crypto-token')
   ;
 
@@ -22,6 +25,7 @@
 exports.App = function(settings) {
 
-    assertSetting("couchDbURL", settings, _.isString);
-    var couch = new CouchDB(settings.couchDbURL);
+    assertSetting("couchServerURL", settings, _.isString);
+    assertSetting("dbName", settings, _.isString);
+    var couch = new CouchDB(settings.couchServerURL,settings.dbName);
     
     var schema = require("./config/couchdb-schema.json");
@@ -69,4 +73,6 @@
     app.use(passport.initialize());
     app.use(passport.session());
+    
+    // various middlewares
     function ensureAuthenticated(req,res,next){
         if (!req.user) {
@@ -78,4 +84,7 @@
     function returnUser(req,res) {
         res.send(200, req.user);
+    }
+    function notImplemented(req,res) {
+        res.send(501,{error:"API not implemented yet."});
     }
 
@@ -112,168 +121,669 @@
         });
 
+    var JSON_MIME = 'application/json';
+    var CSV_MIME = 'application/json';
+    function ensureMIME(mimeType) {
+        return function(req,res,next) {
+            if (!req.accepts(mimeType)) {
+                res.send(406);
+            } else {
+                res.set({
+                    'Content-Type': mimeType
+                });
+                next();
+            }
+        };
+    }
+    
+    function stripAndReturnPrivates(obj) {
+        var priv = {};
+        _.each(obj||{},function(val,key){
+            if (key.substring(0,1) === '_') {
+                priv[key] = val;
+                delete obj[key];
+            }
+        });
+        return priv;
+    }
+
+    function identity(obj) { return obj; }
+    function handleUnknownResponse(status,error){
+        return new HTTPResult(500,{error: "Unexpected database response",
+                                   innerStatus: status, innerResponse: error});
+    }
+    function handleUnknownError(error){
+        return new HTTPResult(500, {error: "Unknown error", innerError: error});
+    }
+    function handleRowValues(result) {
+        return _.map(result.rows, function(item) { return item.value; });
+    }
+    function handleRowDocs(result) {
+        return _.map(result.rows, function(item) { return item.doc; });
+    }
+
+    function getDocumentsOfType (type) {
+        var url = '_design/default/_view/by_type?key='+JSON.stringify(type);
+        return HTTPResult.fromResponsePromise(couch.get(url).response,
+                                              handleUnknownError)
+        .handle({
+            200: handleRowValues,
+            404: function() { return {error: "Cannot find collection of type "+type}; },
+            default: handleUnknownResponse
+        });
+    }
+    function getDocument(id,rev,type) {
+        var opts = {headers:{}};
+        if (rev) {
+            opts.headers['If-Non-Match'] = '"'+rev+'"';
+        }
+        return HTTPResult.fromResponsePromise(couch.get(id,opts).response,
+                                              handleUnknownError)
+        .handle({
+            200: function(doc){
+                if ( doc.type !== type ) {
+                    return new HTTPResult(404,{error:"Document not found."});
+                } else {
+                    var priv = stripAndReturnPrivates(doc);
+                    if ( priv._rev !== rev ) {
+                        doc._id = priv._id;
+                        doc._rev = priv._rev;
+                        return doc;
+                    } else {
+                        return new HTTPResult(304);
+                    }
+                }
+            },
+            304: identity,
+            default: handleUnknownResponse
+        });
+    }
+    function putDocument(id,rev,type,doc) {
+        var priv = stripAndReturnPrivates(doc);
+        if ( doc.type === type && tv4.validateResult(doc, schema) ) {
+            var opts = rev ? {headers:{'If-Match':'"'+rev+'"'}} : {};
+            return HTTPResult.fromResponsePromise(couch.put(id,doc,opts).response,
+                                                  handleUnknownError)
+            .handle({
+                201: function(res){
+                    doc._id = res.id;
+                    doc._rev = res.rev;
+                    return doc;
+                },
+                409: function(error) {
+                    return {error: error.reason};
+                },
+                default: handleUnknownResponse
+            });
+        } else {
+            return new HTTPResult(400,{error: "Document failed schema verification."});
+        }
+    }
+    function deleteDocument(id,rev) {
+        if ( rev ) {
+            var opts = {headers:{'If-Match':'"'+rev+'"'}};
+            return HTTPResult.fromResponsePromise(couch.delete(id,opts).response,
+                                                  handleUnknownError)
+            .handle({
+                200: identity,
+                409: function(error) {
+                    return {error: error.reason};
+                },
+                default: handleUnknownResponse
+            });
+        } else {
+            return new HTTPResult(409, {error: "Cannot identify document revision to delete."});
+        }
+    }
+    function postDocument(type,doc) {
+        var priv = stripAndReturnPrivates(doc);
+        if ( doc.type === type && tv4.validateResult(doc, schema) ) {
+            return HTTPResult.fromResponsePromise(couch.post(doc).response,
+                                                  handleUnknownError)
+            .handle({
+                201: function(response) {
+                    doc._id = response.id;
+                    doc._rev = response.rev;
+                    return doc;
+                },
+                default: handleUnknownResponse
+            });
+        } else {
+            return new HTTPResult(400,{error: "Document failed schema verification."});
+        }
+    }
+
     function makeDocsGet(type) {
         return function(req,res) {
-            var url = '_design/default/_view/by_type?key='+JSON.stringify(type);
-            couch.get(url).then(function(result){
-                var items = _.map(result.rows, function(item) { return item.value; });
-                res.send(200, items);
-            }, function(error){
-                res.send(404, {error: "Cannot find collection"});
+            getDocumentsOfType(type)
+            .handle(res.send.bind(res));
+        };
+    }
+    function makeDocGet_id(type) {
+        return function(req,res) {
+            var id = req.params.id;
+            var rev = etags.parse(req.header('If-Non-Match'))[0];
+            getDocument(id,rev,type)
+            .handle({
+                200: function(doc){
+                    res.set({
+                        'ETag': etags.format([doc._rev])
+                    }).send(200, doc);
+                },
+                default: res.send.bind(res)
             });
         };
     }
-    function makeDocGet_id(type) {
+    function makeDocPut_id(type) {
         return function(req,res) {
             var id = req.params.id;
-            couch.get(id).then(function(result){
-                if ( result.type === type ) {
-                    res.send(200, result);
-                } else {
-                    res.send(404, {error: "Document "+id+" is not a "+type});
-                }
-            }, function(error){
-                res.send(404, {error: "Cannot find survey run "+id});
+            var doc = req.body;
+            var rev = etags.parse(req.header('If-Match'))[0] || (doc && doc._rev);
+            putDocument(id,rev,type,doc)
+            .handle({
+                201: function(doc) {
+                    res.set({
+                        'ETag': etags.format([doc._rev])
+                    }).send(201, doc);
+                },
+                default: res.send.bind(res)
             });
         };
     }
-    function makeDocPut_id(type) {
+    function makeDocDel_id(type) {
         return function(req,res) {
             var id = req.params.id;
             var doc = req.body;
-            if ( doc.type === type && tv4.validateResult(doc, schema) ) {
-                couch.put(id,doc).then(function(result){
-                    res.send(200, result);
-                }, function(error){
-                    res.send(500, error);
+            var rev = etags.parse(req.header('If-Match'))[0] || (doc && doc._rev);
+            deleteDocument(id,rev)
+            .handle(res.send.bind(res));
+        };
+    }
+    function makeDocPost(type) {
+        return function(req,res) {
+            var doc = req.body;
+            postDocument(type,doc)
+            .handle({
+                201: function(doc) {
+                    res.set({
+                        'ETag': etags.format([doc._rev])
+                    }).send(201, doc);
+                },
+                default: res.send.bind(res)
+            });
+        };
+    }
+
+    // Questions
+    function getQuestionsWithCode(code) {
+        var url = '_design/questions/_view/by_code';
+        var query = {include_docs:true,key:code};
+        return HTTPResult.fromResponsePromise(couch.get(url,{query:query}).response,
+                                              handleUnknownError)
+        .handle({
+            200: handleRowDocs,
+            default: handleUnknownResponse
+        });
+    }
+    function getQuestionsWithTopic(topic) {
+        var url = '_design/questions/_view/all_topics';
+        var query = {reduce:false,include_docs:true,key:topic};
+        return HTTPResult.fromResponsePromise(couch.get(url,{query:query}).response,
+                                              handleUnknownError)
+        .handle({
+            200: handleRowDocs,
+            default: handleUnknownResponse
+        });
+    }
+    function getQuestionsWithCategoryAndTopic(category,topic) {
+        var hasTopic = typeof topic !== 'undefined';
+        var url = '_design/questions/_view/all';
+        var query = {reduce:false,include_docs:true,
+                     startkey:hasTopic?[category,topic]:[category],
+                     endkey:hasTopic?[category,topic]:[category,{}]};
+        return HTTPResult.fromResponsePromise(couch.get(url,{query:query}).response,
+                                              handleUnknownError)
+        .handle({
+            200: handleRowDocs,
+            default: handleUnknownResponse
+        });
+    }
+    app.get('/api/questions',
+        ensureAuthenticated,
+        ensureMIME(JSON_MIME),
+        function(req,res) {
+            var hr;
+            if ( 'category' in req.query ) {
+                hr = getQuestionsWithCategoryAndTopic(req.query.category,req.query.topic);
+            } else if ( 'topic' in req.query ) {
+                hr = getQuestionsWithTopic(req.query.topic);
+            } else if ( 'code' in req.query ) {
+                hr = getQuestionsWithCode(req.query.code);
+            }
+            hr.handle(res.send.bind(res));
+        });
+    app.post('/api/questions',
+        ensureAuthenticated,
+        ensureMIME(JSON_MIME),
+        function (req,res) {
+            var doc = req.body;
+            getQuestionsWithCode(doc.code)
+            .handle({
+                200: function(others) {
+                    if ( others.length > 0 ) {
+                        return new HTTPResult(403,{error:"Other question with this code already exists."});
+                    } else {
+                        return postDocument('Question',doc);
+                    }
+                }
+            })
+            .handle(res.send.bind(res));
+        });
+    app.get('/api/questions/:id',
+        ensureAuthenticated,
+        ensureMIME(JSON_MIME),
+        makeDocGet_id('Question'));
+    app.put('/api/questions/:id',
+        ensureAuthenticated,
+        ensureMIME(JSON_MIME),
+        function (req,res) {
+            var id = req.params.id;
+            var doc = req.body;
+            var rev = etags.parse(req.header('If-Match'))[0] || (doc && doc._rev);
+            getQuestionsWithCode(doc.code)
+            .handle({
+                200: function(others) {
+                    if ( others.length > 0 ) {
+                        return new HTTPResult(403,{error:"Other question with this code already exists."});
+                    } else {
+                        return putDocument(id,rev,'Question',doc);
+                    }
+                }
+            })
+            .handle(res.send.bind(res));
+        });
+    app.delete('/api/questions/:id',
+        ensureAuthenticated,
+        ensureMIME(JSON_MIME),
+        makeDocDel_id('Question'));
+
+
+    // Categories and topics
+    app.get('/api/categories',
+        ensureAuthenticated,
+        ensureMIME(JSON_MIME),
+        function(req,res) {
+            var url = '_design/questions/_view/all';
+            HTTPResult.fromResponsePromise(couch.get(url,{query:{reduce:true,group:true,group_level:1}}).response,
+                                           handleUnknownError)
+            .handle({
+                200: function(result) {
+                    return _.map(result.rows, function(item) {
+                        return { name:item.key[0], count:item.value };
+                    });
+                },
+                default: handleUnknownResponse
+            })
+            .handle(res.send.bind(res));
+        });
+    app.get('/api/categories/:category/topics',
+        ensureAuthenticated,
+        ensureMIME(JSON_MIME),
+        function(req,res) {
+            var category = req.params.category;
+            var url = '_design/questions/_view/all';
+            HTTPResult.fromResponsePromise(couch.get(url,{query:{reduce:true,group:true,group_level:2,startkey:[category],endkey:[category,{}]}}).response,
+                                           handleUnknownError)
+            .handle({
+                200: function(result) {
+                    return _.map(result.rows, function(item) { return { name:item.key[1], count:item.value }; });
+                },
+                default: handleUnknownResponse
+            })
+            .handle(res.send.bind(res));
+        });
+    app.get('/api/topics',
+        ensureAuthenticated,
+        ensureMIME(JSON_MIME),
+        function(req,res) {
+            var url = '_design/questions/_view/all_topics';
+            HTTPResult.fromResponsePromise(couch.get(url,{query:{reduce:true,group:true}}).response,
+                                           handleUnknownError)
+            .handle({
+                200: function(result) {
+                    return _.map(result.rows, function(item) { return {name:item.key, count:item.value}; });
+                },
+                default: handleUnknownResponse
+            })
+            .handle(res.send.bind(res));
+        });
+
+    // Surveys
+    app.get('/api/surveys',
+        ensureAuthenticated,
+        ensureMIME(JSON_MIME),
+        function(req,res) {
+            var url;
+            if ( 'drafts' in req.query ) {
+                url = '_design/surveys/_view/drafts';
+            } else if ( 'published' in req.query ) {
+                url = '_design/surveys/_view/published';
+            } else {
+                url = '_design/default/_view/by_type?key='+JSON.stringify('Survey');
+            }
+            HTTPResult.fromResponsePromise(couch.get(url).response,
+                                           handleUnknownError)
+            .handle({
+                200: function(result) {
+                    return _.map(result.rows, function(item) { return item.value; });
+                },
+                default: handleUnknownResponse
+            })
+            .handle(res.send.bind(res));
+        });
+    app.post('/api/surveys',
+        ensureAuthenticated,
+        ensureMIME(JSON_MIME),
+        makeDocPost('Survey'));
+    app.get('/api/surveys/:id',
+        ensureAuthenticated,
+        ensureMIME(JSON_MIME),
+        makeDocGet_id('Survey'));
+    app.put('/api/surveys/:id',
+        ensureAuthenticated,
+        ensureMIME(JSON_MIME),
+        makeDocPut_id('Survey'));
+    app.delete('/api/surveys/:id',
+        ensureAuthenticated,
+        ensureMIME(JSON_MIME),
+        makeDocDel_id('Survey'));
+
+    // SurveyRuns
+    app.get('/api/surveyRuns',
+        ensureAuthenticated,
+        ensureMIME(JSON_MIME),
+        makeDocsGet('SurveyRun'));
+    app.post('/api/surveyRuns',
+        ensureAuthenticated,
+        ensureMIME(JSON_MIME),
+        function(req,res) {
+            var doc = req.body;
+            randomToken()
+            .handle({
+                201: function(token) {
+                    doc.secret = token;
+                    return postDocument('SurveyRun',doc);
+                }
+            })
+            .handle(res.send.bind(res));
+        });
+    app.get('/api/surveyRuns/:id',
+        ensureAuthenticated,
+        ensureMIME(JSON_MIME),
+        makeDocGet_id('SurveyRun'));
+    app.put('/api/surveyRuns/:id',
+        ensureAuthenticated,
+        ensureMIME(JSON_MIME),
+        function (req,res) {
+            var id = req.params.id;
+            var doc = req.body;
+            var rev = etags.parse(req.header('If-Match'))[0] || (doc && doc._rev);
+            var hr;
+            if ( typeof doc.secret === 'undefined' ) {
+                hr = randomToken()
+                .handle({
+                    201: function(token) {
+                        doc.secret = token;
+                        return putDocument(id,rev,'SurveyRun',doc);
+                    }
                 });
             } else {
-                res.send(400,{error: "Document failed schema verification."});
+                hr = putDocument(id,rev,'SurveyRun',doc);
             }
-        };
-    }
-    function makeDocDel_id(type) {
-        return function(req,res) {
-            var id = req.params.id;
-            var rev = req.query.rev;
-            couch.delete(id,rev).then(function(result){
-                res.send(200, result);
-            }, function(error){
-                res.send(500, error);
+            hr.handle(res.send.bind(res));
+        });
+    app.delete('/api/surveyRuns/:id',
+        ensureAuthenticated,
+        ensureMIME(JSON_MIME),
+        makeDocDel_id('SurveyRun'));
+    app.get('/api/surveyRuns/:id/responses',
+        ensureAuthenticated,
+        ensureMIME(JSON_MIME),
+        function(req,res) {
+            var id = req.params.id;
+            getResponsesBySurveyRunId(id)
+            .handle(res.send.bind(res));
+        });
+    app.get('/api/surveyRuns/:id/responses.csv',
+        ensureAuthenticated,
+        ensureMIME(CSV_MIME),
+        function(req, res) {
+            var id = req.params.id;
+            getResponsesBySurveyRunId(id)
+            .handle({
+                200: function(responses) {
+                    var flatResponses = responsesToVariables(responses);
+                    res.set({
+                        'Content-Disposition': 'attachment; filename=surveyRun-'+id+'-responses.csv'
+                    });
+                    res.status(200);
+                    writeObjectsToCSVStream(flatResponses, res);
+                    res.end();
+                },
+                default: res.send.bind(res)
             });
-        };
-    }
-    function makeDocPost(type) {
-        return function(req,res) {
-            var doc = req.body;
-            if ( doc.type === type && tv4.validateResult(doc, schema) ) {
-                couch.post(req.body).then(function(result){
-                    res.send(200, result);
-                }, function(error){
-                    res.send(500, error);
+        });
+    
+    // Responses
+    function getResponsesBySurveyRunId(surveyRunId) {
+        var url = '_design/responses/_view/by_surveyrun?key='+JSON.stringify(surveyRunId);
+        return HTTPResult.fromResponsePromise(couch.get(url).response,
+                                              handleUnknownError)
+        .handle({
+            200: handleRowValues,
+            default: handleUnknownResponse
+        });
+    }
+    app.get('/api/responses',
+        ensureAuthenticated,
+        ensureMIME(JSON_MIME),
+        function(req,res){
+            var hr;
+            if ( 'surveyRunId' in req.query ) {
+                hr = getResponsesBySurveyRunId(req.query.surveyRunId);
+            } else {
+                hr = getDocumentsOfType('Response');
+            }
+            hr.handle(res.send.bind(res));
+        });
+    app.get('/api/responses/:id',
+        ensureAuthenticated,
+        ensureMIME(JSON_MIME),
+        makeDocGet_id('Response'));
+    app.post('/api/responses',
+        ensureAuthenticated,
+        ensureMIME(JSON_MIME),
+        function (req,res) {
+            var doc = req.body;
+            randomToken()
+            .handle({
+                201: function(token) {
+                    doc.secret = token;
+                    return postDocument('Response',doc);
+                }
+            })
+            .handle(res.send.bind(res));
+        });
+    app.put('/api/responses/:id',
+        ensureAuthenticated,
+        ensureMIME(JSON_MIME),
+        function (req,res) {
+            var id = req.params.id;
+            var doc = req.body;
+            var rev = etags.parse(req.header('If-Match'))[0] || (doc && doc._rev);
+            var hr;
+            if ( typeof doc.secret === 'undefined' ) {
+                hr = randomToken()
+                .handle({
+                    201: function(token) {
+                        doc.secret = token;
+                        return putDocument(id,rev,'Response',doc);
+                    }
                 });
             } else {
-                res.send(400,{error: "Document failed schema verification."});
+                hr = putDocument(id,rev,'Response',doc);
             }
-        };
-    }
-    function notImplemented(req,res) {
-        res.send(501,{error:"API not implemented yet."});
-    }
-
-    // Questions
-    app.get('/api/questions',
-        ensureAuthenticated,
-        makeDocsGet('Question'));
-    app.get('/api/questions/:id',
-        ensureAuthenticated,
-        makeDocGet_id('Question'));
-    app.post('/api/questions',
-        ensureAuthenticated,
-        makeDocPost('Question'));
-    app.put('/api/questions/:id',
-        ensureAuthenticated,
-        makeDocPut_id('Question'));
-    app.delete('/api/questions/:id',
-        ensureAuthenticated,
-        makeDocDel_id('Question'));
-    app.get('/api/questions/categories',
-        ensureAuthenticated,
-        notImplemented);
-
-    // Surveys
-    app.get('/api/surveys',
-        ensureAuthenticated,
-        makeDocsGet('Survey'));
-    app.get('/api/surveys/:id',
-        ensureAuthenticated,
-        makeDocGet_id('Survey'));
-    app.post('/api/surveys',
-        ensureAuthenticated,
-        makeDocPost('Survey'));
-    app.put('/api/surveys/:id',
-        ensureAuthenticated,
-        makeDocPut_id('Survey'));
-    app.delete('/api/surveys/:id',
-        ensureAuthenticated,
-        makeDocDel_id('Survey'));
-
-    // SurveyRuns
-    app.get('/api/surveyRuns',
-        ensureAuthenticated,
-        makeDocsGet('SurveyRun'));
-    app.get('/api/surveyRuns/:id',
-        ensureAuthenticated,
-        makeDocGet_id('SurveyRun'));
-    app.get('/api/surveyRuns/:id/responses.csv',
-        ensureAuthenticated,
-        function(req, res) {
-            var id = req.params.id;
-            var url = '_design/responses/_view/by_surveyrun?key='+JSON.stringify(id);
-            couch.get(url).then(function(result){
-                var responses = _.map(result.rows, function(item) { return item.value; });
-                var flatResponses = responsesToVariables(responses);
-                res.set({
-                    'Content-Type': 'text/csv',
-                    'Content-Disposition': 'attachment; filename=surveyRun-'+id+'-responses.csv'
-                });
-                res.status(200);
-                writeObjectsToCSVStream(flatResponses, res);
-                res.end();
-            }, function(error){
-                res.send(404, {error: "Cannot find responses for survey run "+id});
-            });
-        });
-    app.get('/api/surveyRuns/:id/responses',
-        ensureAuthenticated,
+            hr.handle(res.send.bind(res));
+        });
+    app.delete('/api/responses/:id',
+        ensureAuthenticated,
+        ensureMIME(JSON_MIME),
+        makeDocDel_id('Response'));
+
+    //respondents api
+    function isSurveyRunRunning(surveyRun) {
+        var now = new Date();
+        var afterStart = !surveyRun.startDate || now >= new Date(surveyRun.startDate);
+        var beforeEnd = !surveyRun.endDate || now <= new Date(surveyRun.endDate);
+        return afterStart && beforeEnd;
+    }
+    app.get('/api/open/responses/:id',
+        ensureMIME(JSON_MIME),
         function(req,res) {
             var id = req.params.id;
-            var url = '_design/responses/_view/by_surveyrun?key='+JSON.stringify(id);
-            couch.get(url).then(function(result){
-                var responses = _.map(result.rows, function(item) { return item.value; });
-                res.send(200, responses);
-            }, function(error){
-                res.send(404, {error: "Cannot find responses for survey run "+id});
-            });
-        });
-    
-    // Responses
-    app.get('/api/responses',
-        ensureAuthenticated,
-        makeDocsGet('Response'));
-    app.get('/api/responses/:id',
-        ensureAuthenticated,
-        makeDocGet_id('Response'));
-    app.post('/api/responses',
-        ensureAuthenticated,
-        makeDocPost('Response'));
-    app.put('/api/responses/:id',
-        ensureAuthenticated,
-        makeDocPut_id('Response'));
-    app.delete('/api/responses/:id',
-        ensureAuthenticated,
-        makeDocDel_id('Response'));
+            var rev = etags.parse(req.header('If-Non-Match'))[0];
+            var secret = req.query.secret;
+            getDocument(id,rev,'Response')
+            .handle({
+                200: function(response) {
+                    if ( response.secret === secret ) {
+                        return getDocument(response.surveyRunId,[],'SurveyRun')
+                        .handle({
+                            200: function(surveyRun) {
+                                if ( !isSurveyRunRunning(surveyRun) ) {
+                                    return new HTTPResult(404,{error:"Survey is not running anymore."});
+                                } else {
+                                    response._surveyRun = surveyRun;
+                                    return response;
+                                }
+                            }
+                        });
+                    } else {
+                        return new HTTPResult(403,{error: "Wrong secret for response"});
+                    }
+                }
+            })
+            .handle(res.send.bind(res));
+        });
+    app.post('/api/open/responses',
+        ensureMIME(JSON_MIME),
+        function(req,res) {
+            var secret = req.query.secret;
+            var response = req.body;
+            delete response._surveyRun;
+            getDocument(response.surveyRunId,[],'SurveyRun')
+            .handle({
+                200: function(surveyRun) {
+                    if ( surveyRun.secret !== secret ) {
+                        return new HTTPResult(403,{error:"Wrong secret for surveyRun."});
+                    } else if ( !isSurveyRunRunning(surveyRun) ) {
+                        return new HTTPResult(404,{error:"Survey is not running anymore."});
+                    } else if ( surveyRun.mode === 'closed' ) {
+                        return new HTTPResult(403,{error:"Survey is closed and doesn't allow new responses."});
+                    } else {
+                        return randomToken()
+                        .handle({
+                            201: function(token) {
+                                response.secret = token;
+                                return postDocument('Response',response)
+                                .handle({
+                                    201: function(doc){
+                                        doc._surveyRun = surveyRun;
+                                        return doc;
+                                    }
+                                });
+                            }
+                        });
+                    }
+                }
+            })
+            .handle(res.send.bind(res));
+        });
+    app.put('/api/open/responses/:id',
+        ensureMIME(JSON_MIME),
+        function(req,res){
+            var id = req.params.id;
+            var doc = req.body;
+            var rev = etags.parse(req.header('If-Match'))[0] || (doc && doc._rev);
+            var secret = req.query.secret || doc.secret;
+            delete doc._surveyRun;
+            getDocument(id,[],'Response')
+            .handle({
+                200: function(prev) {
+                    if ( prev.secret !== secret ) {
+                        return new HTTPResult(403,{error: "Secrets are not the same."});
+                    } else if ( prev.surveyRunId !== doc.surveyRunId ) {
+                        return new HTTPResult(400,{error:"Response cannot change it's surveyRunId."});
+                    } else {
+                        doc.secret = secret; // restore in case it got lost or was changed
+                        return getDocument(doc.surveyRunId,[],'SurveyRun')
+                        .handle({
+                            200: function(surveyRun) {
+                                if ( !isSurveyRunRunning(surveyRun) ) {
+                                    return new HTTPResult(404,{error:"Survey is not running anymore."});
+                                } else {
+                                    return putDocument(id,rev,'Response',doc)
+                                    .handle({
+                                        200: function(doc) {
+                                            doc._surveyRun = surveyRun;
+                                            return new HTTPResult(201,doc);
+                                        }
+                                    });
+                                }
+                            }
+                        });
+                    }
+                }
+            })
+            .handle(res.send.bind(res));
+        });
+    app.delete('/api/open/responses/:id',
+        ensureMIME(JSON_MIME),
+        function(req,res){
+            var id = req.params.id;
+            var doc = req.body;
+            var rev = etags.parse(req.header('If-Match'))[0] || (doc && doc._rev);
+            var secret = req.query.secret || doc.secret;
+            delete doc._surveyRun;
+            getDocument(id,[],'Response')
+            .handle({
+                200: function(prev) {
+                    if ( prev.secret !== secret ) {
+                        return new HTTPResult(403,{error: "Secrets are not the same."});
+                    } else {
+                        return deleteDocument(id,rev,doc);
+                    }
+                }
+            })
+            .handle(res.send.bind(res));
+        });
+
+    // uuids
+    app.get('/api/uuids',
+        ensureAuthenticated,
+        ensureMIME(JSON_MIME),
+        function(req,res){
+            var count = (req.query.count && parseInt(req.query.count,10)) || 1;
+            HTTPResult.fromResponsePromise(couch.uuids({query:{count:count}}).response,
+                                           handleUnknownError)
+            .handle({
+                200: function(res) {
+                    return res.uuids;
+                },
+                default: handleUnknownResponse
+            })
+            .handle(res.send.bind(res));
+        });
 
     return app;
-
 }
 
@@ -341,2 +851,13 @@
     });
 }
+
+function randomToken() {
+    var result = new HTTPResult();
+    cryptoken(8)
+    .then(function(token){
+        result.set(201,token);
+    }, function(ex){
+        result.set(500,{error:"Cannot generate secrets.", innerError: ex});
+    });
+    return result;
+}
Index: Dev/trunk/src/server/bin/check-db.js
===================================================================
--- Dev/trunk/src/server/bin/check-db.js	(revision 486)
+++ Dev/trunk/src/server/bin/check-db.js	(revision 487)
@@ -1,5 +1,6 @@
 var env = require('../env')
-  , checkCouch = require('../config/check-couchdb');
-checkCouch(env.couchDbURL)
+  , checkCouch = require('../config/check-couchdb')
+  ;
+checkCouch(env.couchServerURL,env.dbName)
 .then(function(res){
     console.log("done",res);
Index: Dev/trunk/src/server/bin/config-db-for-upgrade.js
===================================================================
--- Dev/trunk/src/server/bin/config-db-for-upgrade.js	(revision 487)
+++ Dev/trunk/src/server/bin/config-db-for-upgrade.js	(revision 487)
@@ -0,0 +1,10 @@
+var env = require('../env')
+  , configCouch = require('../config/config-couchdb')
+  , designDocs = require('../config/couchdb-upgrade-design-docs')
+  ;
+configCouch(env.couchServerURL,env.dbName,designDocs)
+.then(function(res){
+    console.log("done",res);
+}, function(err){
+    console.error("fail",err);
+});
Index: Dev/trunk/src/server/bin/config-db.js
===================================================================
--- Dev/trunk/src/server/bin/config-db.js	(revision 486)
+++ Dev/trunk/src/server/bin/config-db.js	(revision 487)
@@ -1,5 +1,7 @@
 var env = require('../env')
-  , checkCouch = require('../config/config-couchdb');
-checkCouch(env.couchServerURL)
+  , configCouch = require('../config/config-couchdb')
+  , designDocs = require('../config/couchdb-design-docs')
+  ;
+configCouch(env.couchServerURL,env.dbName,designDocs)
 .then(function(res){
     console.log("done",res);
Index: Dev/trunk/src/server/bin/heroku.js
===================================================================
--- Dev/trunk/src/server/bin/heroku.js	(revision 486)
+++ Dev/trunk/src/server/bin/heroku.js	(revision 487)
@@ -1,11 +1,14 @@
-var env = require('../env');
-var configCouch = require('../config/config-couchdb');
+var env = require('../env')
+  , configCouch = require('../config/config-couchdb')
+  , designDocs = require('../config/config-couchdb')
+  ;
 
 console.log("Running on",env.couchServerURL);
 
 require('../app').App({
-    couchDbURL: env.couchDbURL
+    couchServerURL: env.couchServerURL,
+    dbName: env.dbName
 }).then(function(app){
-    configCouch(env.couchServerURL);
+    configCouch(env.couchServerURL,env.dbName,designDocs);
     return app;
 }).then(function(app){
Index: Dev/trunk/src/server/bin/upgrade-db-1-to-2.js
===================================================================
--- Dev/trunk/src/server/bin/upgrade-db-1-to-2.js	(revision 487)
+++ Dev/trunk/src/server/bin/upgrade-db-1-to-2.js	(revision 487)
@@ -0,0 +1,19 @@
+var env = require('../env')
+  , upgradeCouch = require('../config/upgrade-couchdb')
+  , cryptoken = require('../util/crypto-token')
+  , Q = require('q')
+  ;
+upgradeCouch(env.couchServerURL,env.dbName,function(doc){
+    if ( (doc.type === 'Response' || doc.type === 'SurveyRun') && !doc.secret ) {
+        return cryptoken(8).then(function(token){
+            doc.secret = token;
+            return doc;
+        });
+    } else {
+        return Q.reject();
+    }
+}).then(function(res){
+    console.log("done",res);
+}, function(err){
+    console.error("fail",err);
+});
Index: Dev/trunk/src/server/config/check-couchdb.js
===================================================================
--- Dev/trunk/src/server/config/check-couchdb.js	(revision 486)
+++ Dev/trunk/src/server/config/check-couchdb.js	(revision 487)
@@ -7,6 +7,6 @@
 var schema = require('./couchdb-schema');
 
-module.exports = function(couchDbURL) {
-    var server = new CouchDB(couchDbURL);
+module.exports = function(couchServerURL,dbName) {
+    var server = new CouchDB(couchServerURL,dbName);
     var designRe = /^_design\//;
     return server.get('/_all_docs')
Index: Dev/trunk/src/server/config/config-couchdb.js
===================================================================
--- Dev/trunk/src/server/config/config-couchdb.js	(revision 486)
+++ Dev/trunk/src/server/config/config-couchdb.js	(revision 487)
@@ -5,8 +5,8 @@
   ;
 
-var designDocs = require('./couchdb-design-docs');
+module.exports = function(couchServerURL, dbName, designDocs) {
+    var server = new CouchDB(couchServerURL);
 
-module.exports = function(couchServerURL) {
-    var server = new CouchDB(couchServerURL);
+    if ( !designDocs ) { throw new Error("Forgot to pass design docs to checkCouch."); }
 
     console.log("Configuring CouchDB for QED");
@@ -14,8 +14,8 @@
     return server.get('')
     .then(function(info){
-        if (info.version !== "1.2.0" ) {
+        if (info.version !== "1.4.0" ) {
             console.log("Found "+info.version+", only tested with CouchDB 1.2.0");
         } else {
-            console.log("CouchDB 1.2.0 found");
+            console.log("CouchDB 1.4.0 found");
         }
     }).then(function(res){
Index: Dev/trunk/src/server/config/couchdb-design-docs.js
===================================================================
--- Dev/trunk/src/server/config/couchdb-design-docs.js	(revision 486)
+++ Dev/trunk/src/server/config/couchdb-design-docs.js	(revision 487)
@@ -11,5 +11,5 @@
 
     "qed/schemaInfo": {
-        version: "1"
+        version: "2"
     },
 
@@ -31,5 +31,7 @@
         language: "javascript",
         validate_doc_update: function(newDoc, oldDoc, userCtx, secObj) {
-            if ( oldDoc && oldDoc.publicationDate ) { throw({forbidden:'Published documents cannot be modified.'}); }
+            if ( oldDoc && oldDoc.publicationDate ) {
+                throw({forbidden:'Published documents cannot be modified.'});
+            }
         },
         views: {
@@ -59,8 +61,8 @@
                     if ( doc.categories && doc.categories.length > 0 ) {
                         for ( var i = 0; i < doc.categories.length; i++ ) { 
-                            emit([doc.categories[i],doc.topic||null],1);
+                            emit([doc.categories[i],doc.topic||"(default)"],1);
                         } 
                     } else { 
-                        emit([null,null],1); 
+                        emit(["(default)","(default)"],1); 
                     } 
                 },
@@ -72,8 +74,8 @@
                     if ( doc.categories && doc.categories.length > 0 ) {
                         for ( var i = 0; i < doc.categories.length; i++ ) {
-                            emit([doc.categories[i],doc.topic||null],1);
+                            emit([doc.categories[i],doc.topic||"(default)"],1);
                         }
                     } else {
-                        emit([null,null],1);
+                        emit(["(default)","(default)"],1);
                     }
                 },
@@ -83,14 +85,20 @@
                 map: function(doc){
                     if( doc.type !== 'Question' ){ return; }
-                    emit(doc.topic);
+                    emit(doc.topic||"(default)",1);
                 },
-                reduce: function(key, values, rereduce) { return null; }
+                reduce: function(key, values, rereduce) { return sum(values); }
             },
             published_topics: {
                 map: function(doc){
                     if ( doc.type !== 'Question' || !doc.publicationDate ) { return; }
-                    emit(doc.topic);
+                    emit(doc.topic||"(default)",1);
                 },
-                reduce: function(key, values, rereduce) { return null; }
+                reduce: function(key, values, rereduce) { return sum(values); }
+            },
+            by_code: {
+                map: function(doc){
+                    if ( doc.type !== 'Question' ) { return; }
+                    emit(doc.code,doc);
+                }
             }
         }
Index: Dev/trunk/src/server/config/couchdb-schema.json
===================================================================
--- Dev/trunk/src/server/config/couchdb-schema.json	(revision 486)
+++ Dev/trunk/src/server/config/couchdb-schema.json	(revision 487)
@@ -1,129 +1,144 @@
 {
+  "$schema": "http://json-schema.org/draft-04/schema#",
   "title": "QED Object Schema",
-  "version": "1",
+  "version": "2",
   "type": "object",
   "oneOf": [
-    { "$ref": "#/schemaInfo" },
-    { "$ref": "#/docTypes/any" }
+    { "$ref": "#/definitions/schemaInfo" },
+    { "$ref": "#/definitions/docs/any" }
   ],
-  "schemaInfo": {
-    "type": "object",
-    "properties": {
-        "_id": { "type": "string", "pattern": "schemaInfo" },
-        "_rev": { "type": "string", "optional": true },
-        "version": { "type": "string" }
-    },
-    "additionalProperties": false
-  },
-  "docTypes": {
-    "any": {
+  "definitions": {
+    "schemaInfo": {
       "type": "object",
-      "oneOf": [
-        { "$ref": "#/docTypes/Question" },
-        { "$ref": "#/docTypes/Survey" },
-        { "$ref": "#/docTypes/SurveyRun" },
-        { "$ref": "#/docTypes/Response" }
-      ]
-    },
-    "Question": {
       "properties": {
-        "type": { "type": "string", "pattern": "Question" },
-        "_id": { "type": "string", "optional": true },
-        "_rev": { "type": "string", "optional": true },
-        "categories": { "type": "array", "items": { "type": "string" } },
-        "code": { "type": "string" },
-        "content": { "type": "array", "items": { "$ref": "#/contentTypes/any" } },
-        "description": { "type": "string", "optional": true },
-        "publicationDate": { "type": "string", "pattern": "", "optional": true },
-        "title": { "type": "string" },
-        "topic": { "type": "string", "optional": true }
+          "_id": { "type": "string", "pattern": "schemaInfo" },
+          "_rev": { "type": "string" },
+          "version": { "type": "string" }
       },
+      "required": ["_id","version"],
       "additionalProperties": false
     },
-    "Survey": {
-      "type": "object",
-      "properties": {
-        "type": { "type": "string", "pattern": "Survey" },
-        "_id": { "type": "string", "optional": true },
-        "_rev": { "type": "string", "optional": true },
-        "publicationDate": { "type": "string", "pattern": "", "optional": true },
-        "questions": { "type": "array", "items": { "$ref": "#/docTypes/Question" } },
-        "title": { "type": "string" }
+    "docs": {
+      "any": {
+        "type": "object",
+        "oneOf":[
+          { "$ref": "#/definitions/docs/Question" },
+          { "$ref": "#/definitions/docs/Survey" },
+          { "$ref": "#/definitions/docs/SurveyRun" },
+          { "$ref": "#/definitions/docs/Response" }
+        ]
       },
-      "additionalProperties": false
-    },
-    "SurveyRun": {
-      "type": "object",
-      "properties": {
-        "type": { "type": "string", "pattern": "SurveyRun" },
-        "_id": { "type": "string", "optional": true },
-        "_rev": { "type": "string", "optional": true },
-        "description": { "type": "string" },
-        "endDate": { "type": "string", "pattern": "" },
-        "mode": { "type": "string", "enum": [ "open", "closed" ] },
-        "startDate": { "type": "string", "pattern": "" },
-        "survey": { "$ref": "#/docTypes/Survey" },
-        "title": { "type": "string" }
-      },
-      "additionalProperties": false
-    },
-    "Response": {
-      "type": "object",
-      "properties": {
-        "type": { "type": "string", "pattern": "Response" },
-        "_id": { "type": "string", "optional": true },
-        "_rev": { "type": "string", "optional": true },
-        "answers": { "type": "object" },
-        "publicationDate": { "type": "string", "pattern": "", "optional": true },
-        "surveyRunId": { "type": "string" }
-      },
-      "additionalProperties": false
-    }
-  },
-  "contentTypes": {
-    "any": {
-      "type": "object",
-      "oneOf": [
-        { "$ref": "#/contentTypes/Text" },
-        { "$ref": "#/contentTypes/StringInput" },
-        { "$ref": "#/contentTypes/ScaleInput" }
-      ]
-    },
-    "Text": {
+      "Question": {
         "type": "object",
         "properties": {
-            "type": { "type": "string", "pattern": "Text" },
-            "text": { "type": "string" }
+          "type": { "type": "string", "pattern": "Question" },
+          "_id": { "type": "string" },
+          "_rev": { "type": "string" },
+          "categories": { "type": "array", "items": { "type": "string" } },
+          "code": { "type": "string" },
+          "content": { "type": "array", "items": { "$ref": "#/definitions/content/any" } },
+          "description": { "type": "string" },
+          "publicationDate": { "type": "string", "format": "datetime" },
+          "title": { "type": "string" },
+          "topic": { "type": "string" }
         },
+        "required": ["type","categories","code","content","title"],
         "additionalProperties": false
-    },
-    "StringInput": {
+      },
+      "Survey": {
         "type": "object",
         "properties": {
-            "type": { "type": "string", "pattern": "StringInput" },
-            "text": { "type": "string" }
+          "type": { "type": "string", "pattern": "Survey" },
+          "_id": { "type": "string" },
+          "_rev": { "type": "string" },
+          "publicationDate": { "type": "string", "format": "datetime" },
+          "questions": { "type": "array", "items": { "$ref": "#/definitions/docs/Question" } },
+          "title": { "type": "string" }
         },
+        "required": ["type","questions","title"],
         "additionalProperties": false
-    },
-    "ScaleInput": {
+      },
+      "SurveyRun": {
         "type": "object",
         "properties": {
-            "type": { "type": "string", "pattern": "ScaleInput" },
-            "minLabel": { "type": "string", "optional": true },
-            "min": { "type": "integer" },
-            "max": { "type": "integer" },
-            "maxLabel": { "type": "string", "optional": true },
-            "naLabel": { "type": "string", "optional": true },
-            "items": { "type": "array", "items": {
-                "type": "object",
-                "properties": {
-                    "text": { "type": "string" },
-                    "minLabel": { "type": "string", "optional": true },
-                    "maxLabel": { "type": "string", "optional": true }
-                },
-                "additionalProperties": false
-            } }
+          "type": { "type": "string", "pattern": "SurveyRun" },
+          "_id": { "type": "string" },
+          "_rev": { "type": "string" },
+          "description": { "type": "string" },
+          "endDate": { "type": "string", "format": "datetime" },
+          "mode": { "type": "string", "enum": [ "open", "closed" ] },
+          "secret": { "type": "string", "minLength": 8 },
+          "startDate": { "type": "string", "format": "datetime" },
+          "survey": { "$ref": "#/definitions/docs/Survey" },
+          "title": { "type": "string" }
         },
+        "required": ["type","description","mode","secret","survey","title"],
         "additionalProperties": false
+      },
+      "Response": {
+        "type": "object",
+        "properties": {
+          "type": { "type": "string", "pattern": "Response" },
+          "_id": { "type": "string" },
+          "_rev": { "type": "string" },
+          "answers": { "type": "object" },
+          "publicationDate": { "type": "string", "format": "datetime" },
+          "secret": { "type": "string", "minLength": 8 },
+          "surveyRunId": { "type": "string" }
+        },
+        "required": ["type","answers","secret","surveyRunId"],
+        "additionalProperties": false
+      }
+    },
+    "content":{
+      "any": {
+        "type": "object",
+        "oneOf": [
+          { "$ref": "#/definitions/content/Text" },
+          { "$ref": "#/definitions/content/StringInput" },
+          { "$ref": "#/definitions/content/ScaleInput" }
+        ]
+      },
+      "Text": {
+        "type": "object",
+        "properties": {
+          "type": { "type": "string", "pattern": "Text" },
+          "text": { "type": "string" }
+        },
+        "required": ["type","text"],
+        "additionalProperties": false
+      },
+      "StringInput": {
+        "type": "object",
+        "properties": {
+          "type": { "type": "string", "pattern": "StringInput" },
+          "text": { "type": "string" }
+        },
+        "required":["type","text"],
+        "additionalProperties": false
+      },
+      "ScaleInput": {
+        "type": "object",
+        "properties": {
+          "type": { "type": "string", "pattern": "ScaleInput" },
+          "minLabel": { "type": "string" },
+          "min": { "type": "integer" },
+          "max": { "type": "integer" },
+          "maxLabel": { "type": "string" },
+          "naLabel": { "type": "string" },
+          "items": { "type": "array", "items": {
+            "type": "object",
+            "properties": {
+              "text": { "type": "string" },
+              "minLabel": { "type": "string" },
+              "maxLabel": { "type": "string" }
+            },
+            "required":["text"],
+            "additionalProperties": false
+          } }
+        },
+        "required":["type","min","max","items"],
+        "additionalProperties": false
+      }
     }
   }
Index: Dev/trunk/src/server/config/couchdb-upgrade-design-docs.js
===================================================================
--- Dev/trunk/src/server/config/couchdb-upgrade-design-docs.js	(revision 487)
+++ Dev/trunk/src/server/config/couchdb-upgrade-design-docs.js	(revision 487)
@@ -0,0 +1,11 @@
+module.exports = {
+
+    "qed/schemaInfo": {
+        version: "-1"
+    },
+
+    "qed/_design/default": {
+        __configAction: "replace"
+    }
+
+};
Index: Dev/trunk/src/server/config/upgrade-couchdb.js
===================================================================
--- Dev/trunk/src/server/config/upgrade-couchdb.js	(revision 487)
+++ Dev/trunk/src/server/config/upgrade-couchdb.js	(revision 487)
@@ -0,0 +1,38 @@
+var Q = require('q')
+  , _ = require('underscore')
+  , tv4 = require('tv4')
+  , CouchDB = require('../util/couch').CouchDB
+  ;
+
+var schema = require('./couchdb-schema');
+
+module.exports = function(couchServerURL,dbName,upgrade) {
+    var server = new CouchDB(couchServerURL,dbName);
+    var designRe = /^_design\//;
+    return server.get('/_all_docs')
+    .then(function(allDocs){
+        return _.chain(allDocs.rows)
+        .filter(function(doc){
+            return doc.id.match(designRe) === null;
+        })
+        .reduce(function(memo, doc){
+            return memo.then(function(result){
+                return server.get(doc.id)
+                .then(function(doc){
+                    Q.when(upgrade(doc))
+                    .then(function(newDoc){
+                        return server.put(doc._id,newDoc)
+                        .then(function(){
+                            result[doc._id] = true;
+                            return result;
+                        });
+                    },function(){
+                        result[doc._id] = false;
+                    });
+                    return result;
+                });
+            });
+        }, Q.resolve({}))
+        .value();
+    }).all();
+};
Index: Dev/trunk/src/server/env.js
===================================================================
--- Dev/trunk/src/server/env.js	(revision 486)
+++ Dev/trunk/src/server/env.js	(revision 487)
@@ -3,4 +3,4 @@
     port: process.env.PORT || 5000,
     couchServerURL: couchServerURL,
-    couchDbURL: couchServerURL+'qed',
+    dbName: 'qed'
 };
Index: Dev/trunk/src/server/util/couch.coffee
===================================================================
--- Dev/trunk/src/server/util/couch.coffee	(revision 486)
+++ Dev/trunk/src/server/util/couch.coffee	(revision 487)
@@ -6,32 +6,42 @@
  
 class CouchDB
-    constructor: (url) ->
-        @url = normalizeURL url
-    get: (id) ->
+    constructor: (url,db) ->
+        @serverURL = normalizeURL url
+        @db = db
+        if @db
+            @url = "#{@serverURL}/#{@db}"
+        else
+            @url = "#{@serverURL}"
+    get: (id, opts) ->
         url = "#{@url}/#{id}"
-        couchRequest 'GET', url
-    post: (doc) ->
-        url = "#{@url}"
-        couchRequest 'POST', url, doc
-    put: (id, doc) ->
+        couchRequest 'GET', url, null, opts
+    post: (doc, opts) ->
+        url = "#{@url}/"
+        couchRequest 'POST', url, doc, opts
+    put: (id, doc, opts) ->
         url = "#{@url}/#{id}"
-        couchRequest 'PUT', url, doc
-    delete: (id, rev) ->
-        url = "#{@url}/#{id}?rev=#{rev}"
-        couchRequest 'DELETE', url
+        couchRequest 'PUT', url, doc, opts
+    delete: (id, opts) ->
+        url = "#{@url}/#{id}"
+        couchRequest 'DELETE', url, null, opts
+    uuids: (opts) ->
+        url = "#{@serverURL}/_uuids"
+        couchRequest 'GET', url, null, opts
 
 normalizeURL = (url) ->
     url.replace /\/$/, ''
 
-couchRequest = (method, url, data) ->
+couchRequest = (method, url, body, opts) ->
     options =
         method: method,
         headers:
-            'content-type': 'application/json; charset=utf-8'
-            'accept': 'application/json'
-        body: JSON.stringify (stringifyFunctions (data || {}))
+            'Content-Type': 'application/json; charset=utf-8'
+            'Accept': 'application/json'
+        body: JSON.stringify (stringifyFunctions (body || {}))
+    if opts
+        options.qs = createQueryObj opts.query if opts.query
+        _.extend options.headers, opts.headers if opts.headers
     req = request(url, options)
-    req.response
-    .then (res) =>
+    res = req.response.then (res) =>
         req.then (res) =>
             JSON.parse res
@@ -40,5 +50,18 @@
     , (err) =>
         Q.reject err
+    res.response = req.response.then (res) =>
+        statusCode: res.statusCode
+        headers: res.headers
+        body: JSON.parse res.body
+    , (err) =>
+        Q.reject err
+    res
 
+createQueryObj = (obj) ->
+    newObj = {}
+    _.each obj, (val,key) ->
+        newObj[key] = JSON.stringify val
+    newObj
+        
 stringifyFunctions = (value) ->
     if value is null
Index: Dev/trunk/src/server/util/crypto-token.js
===================================================================
--- Dev/trunk/src/server/util/crypto-token.js	(revision 487)
+++ Dev/trunk/src/server/util/crypto-token.js	(revision 487)
@@ -0,0 +1,14 @@
+var Q = require('q')
+  , crypto = require('crypto');
+
+module.exports = function(bytes) {
+    var q = Q.defer();
+    crypto.randomBytes(bytes || 8, function(ex, buf) {
+        if ( ex ) {
+            q.reject(ex);
+        } else {
+            q.resolve(buf.toString('hex'));
+        }
+    });
+    return q.promise;
+};
Index: Dev/trunk/src/server/util/etags.js
===================================================================
--- Dev/trunk/src/server/util/etags.js	(revision 487)
+++ Dev/trunk/src/server/util/etags.js	(revision 487)
@@ -0,0 +1,14 @@
+var _ = require('underscore');
+
+var revRe = /"([^"]*)"/g;
+
+exports.parse = function(header) {
+    return _.map((header && header.match(revRe)) || [], function(str){
+        return str.substring(1,str.length-1);
+    });
+};
+
+exports.format = function(etags) {
+    return _.map(etags || [], function(str) { return '"'+str+'"'; })
+            .join(', ');
+};
Index: Dev/trunk/src/server/util/http-result.js
===================================================================
--- Dev/trunk/src/server/util/http-result.js	(revision 487)
+++ Dev/trunk/src/server/util/http-result.js	(revision 487)
@@ -0,0 +1,78 @@
+// <init> :: status? -> result? -> HTTPResult
+function HTTPResult(status,result) {
+    this.status = 0;
+    this.result = null;
+    this._handlers = [];
+    if ( status ) {
+        this.set(status,result);
+    }
+}
+
+HTTPResult.fromResponsePromise = function(promise,errorHandler) {
+    var result = new HTTPResult();
+    promise.then(function(response){
+        result.set(response.statusCode,response.body);
+    }, errorHandler && function(error){
+        var ret = errorHandler(error);
+        if ( ret.constructor === HTTPResult ) {
+            ret.handle(result.set.bind(result));
+        } else {
+            result.set(500,ret);
+        }
+    });
+    return result;
+};
+
+// isSet :: Bool
+HTTPResult.prototype.isSet = function() {
+    return this.status !== 0;
+};
+
+// set :: status -> result? -> ()
+HTTPResult.prototype.set = function(status,result) {
+    if ( this.isSet() ) { throw new Error("Result was already set."); }
+    if ( typeof status !== 'number' ) { throw new Error("Status must be a number."); }
+    this.status = status;
+    this.result = result;
+    for ( var i = 0; i < this._handlers.length; i++ ) {
+        this._fire(this._handlers[i]);
+    }
+};
+
+// _fire :: (status -> result -> Either HTTPResult Any) -> ()
+HTTPResult.prototype._fire = function(f) {
+    var ret = f(this.status,this.result);
+    if ( ret.constructor === HTTPResult ) {
+        ret.handle(f._next.set.bind(f._next));
+    } else {
+        f._next.set(this.status,ret);
+    }
+};
+
+/* handle :: Either (status -> result -> Either HTTPResult Any)
+ *                  {number: (result -> Either HTTPResult Any),
+ *                   'default': (status -> result -> Either HTTPResult Any)}
+ *           -> HTTPResult
+ */
+HTTPResult.prototype.handle = function(fOrObj) {
+    var f = typeof fOrObj === 'function' ?
+            fOrObj :
+            function(status,result) {
+                if ( status in fOrObj ) {
+                    return fOrObj[status](result);
+                } else if ( 'default' in fOrObj ) {
+                    return fOrObj['default'](status,result);
+                } else {
+                    return result;
+                }
+            };
+    f._next = new HTTPResult();
+    if ( this.isSet() ) {
+        this._fire(f);
+    } else {
+        this._handlers.push(f);
+    }
+    return f._next;
+};
+
+module.exports = HTTPResult;
