#6
authoradam <adamansky@gmail.com>
Wed, 19 Dec 2012 17:52:48 +0000 (00:52 +0700)
committeradam <adamansky@gmail.com>
Wed, 19 Dec 2012 17:52:48 +0000 (00:52 +0700)
node/bin/cli.js [moved from node/bin/ejc.js with 88% similarity]
node/ejdb.js
node/ejdb_native.cc
node/tests/t2.js
package.json

similarity index 88%
rename from node/bin/ejc.js
rename to node/bin/cli.js
index 1167280..ab00957 100644 (file)
@@ -57,13 +57,7 @@ repl.on("exit", function() {
 
 function dbstatus(cdb) {
     if (cdb) {
-        var status = {
-            dbpath : cdb.dbpath,
-            opened : !!(cdb.jb && cdb.jb.isOpen()),
-            collections : []
-        };
-        //todo fill collections and indexes
-        return status;
+        return cdb.jb.getDBMeta();
     } else {
         return {};
     }
index 5015b92..332266b 100644 (file)
@@ -125,17 +125,21 @@ EJDB.prototype.removeCollection = function(cname, prune, cb) {
  * If a saved object does not have `_id` it will be autogenerated.
  * To identify and update object it should contains `_id` property.
  *
+ * If callback is not provided this function will be synchronous.
+ *
  * Call variations:
- *      - save(cname, <json object>|<Array of json objects>, [cb])
- *      - save(cname, <json object>|<Array of json objects>, [options], [cb])
+ *      - save(cname, <json object>|<Array of json objects>, options)
+ *      - save(cname, <json object>|<Array of json objects>, cb)
+ *      - save(cname, <json object>|<Array of json objects>, options, cb)
  *
  * @param {String} cname Name of collection.
  * @param {Array|Object} jsarr Signle JSON object or array of JSON objects to save
  * @param {Function} [cb] Callback function with arguments: (error, {Array} of OIDs for saved objects)
+ * @return {Array} of OIDs for saved objects in synchronous mode otherwise undefined.
  */
 EJDB.prototype.save = function(cname, jsarr, opts, cb) {
     if (!jsarr) {
-        return;
+        return [];
     }
     if (jsarr.constructor !== Array) {
         jsarr = [jsarr];
@@ -144,13 +148,7 @@ EJDB.prototype.save = function(cname, jsarr, opts, cb) {
         cb = opts;
         opts = null;
     }
-    return this._impl.save(cname, jsarr, (opts || {}), function(err, oids) {
-        if (err) {
-            if (cb) {
-                cb(err);
-            }
-            return;
-        }
+    var postprocess = function(oids) {
         //Assign _id property for newly created objects
         for (var i = jsarr.length - 1; i >= 0; --i) {
             var so = jsarr[i];
@@ -158,20 +156,32 @@ EJDB.prototype.save = function(cname, jsarr, opts, cb) {
                 so["_id"] = oids[i];
             }
         }
-        if (cb) {
-            cb(err, oids);
-        }
-    });
+    };
+    if (cb == null) {
+        postprocess(this._impl.save(cname, jsarr, (opts || {})));
+        return jsarr;
+    } else {
+        return this._impl.save(cname, jsarr, (opts || {}), function(err, oids) {
+            if (err) {
+                cb(err);
+                return;
+            }
+            postprocess(oids);
+            cb(null, oids);
+        });
+    }
 };
 
 
 /**
  * Loads JSON object identified by OID from the collection.
+ * If callback is not provided this function will be synchronous.
  *
  * @param {String} cname Name of collection
  * @param {String} oid Object identifier (OID)
- * @param {Function} cb  Callback function with arguments: (error, obj)
+ * @param {Function} [cb]  Callback function with arguments: (error, obj)
  *        `obj`:  Retrieved JSON object or NULL if it is not found.
+ * @return Retrieved JSON object or NULL if it is not found in synchronous mode otherwise undefined.
  */
 EJDB.prototype.load = function(cname, oid, cb) {
     return this._impl.load(cname, oid, cb);
@@ -179,15 +189,54 @@ EJDB.prototype.load = function(cname, oid, cb) {
 
 /**
  * Removes JSON object from the collection.
+ * If callback is not provided this function will be synchronous.
  *
  * @param {String} cname Name of collection
  * @param {String} oid Object identifier (OID)
- * @param {Function} cb  Callback function with arguments: (error)
+ * @param {Function} [cb]  Callback function with arguments: (error)
+ * @return {undefined}
  */
 EJDB.prototype.remove = function(cname, oid, cb) {
     return this._impl.remove(cname, oid, cb);
 };
 
+
+/*
+ * - (cname, qobj, [cb])
+ * - (cname, qobj, hints, [cb])
+ * - (cname, qobj, qobjarr, [cb])
+ * - (cname, qobj, qobjarr, hints, [cb])
+ */
+function parseQueryArgs(args) {
+    var cname, qobj, orarr, hints, cb;
+    cname = args.shift();
+    if (typeof cname !== "string") {
+        throw new Error("Collection name 'cname' argument must be specified");
+    }
+    qobj = args.shift();
+    var next = args.shift();
+    if (next !== undefined) {
+        if (next.constructor === Array) {
+            orarr = next;
+            next = args.shift();
+        } else if (typeof next === "object") {
+            hints = next;
+            orarr = null;
+            next = args.shift();
+        }
+        if (!hints && typeof next === "object") {
+            hints = next;
+            next = args.shift();
+        }
+        if (typeof next === "function") {
+            cb = next;
+        }
+    } else {
+        orarr = hints = cb = null;
+    }
+    return [cname, (qobj || {}), (orarr || []), (hints || {}), (cb || null)];
+}
+
 /**
  * Execute query on collection.
  *
@@ -236,11 +285,10 @@ EJDB.prototype.remove = function(cname, oid, cb) {
  *
  *  NOTE: It is better to execute update queries with `$onlycount=true` hint flag
  *        or use the special `update()` method to avoid unnecessarily data fetching.
- *
  *  NOTE: Negate operations: $not and $nin not using indexes
- *  so they can be slow in comparison to other matching operations.
- *
+ *        so they can be slow in comparison to other matching operations.
  *  NOTE: Only one index can be used in search query operation.
+ *  NOTE: If callback is not provided this function will be synchronous.
  *
  *  QUERY HINTS (specified by `hints` argument):
  *      - $max Maximum number in the result set
@@ -274,230 +322,172 @@ EJDB.prototype.remove = function(cname, oid, cb) {
  *      - Cursor#close() Closes cursor and free cursor resources. Cursor cant be used in closed state.
  *
  * Call variations of find():
- *       - find(cname, qobj, cb)
- *       - find(cname, qobj, hints, cb)
- *       - find(cname, qobj, qobjarr, cb)
- *       - find(cname, qobj, qobjarr, hints, cb)
+ *       - find(cname, qobj, [cb])
+ *       - find(cname, qobj, hints, [cb])
+ *       - find(cname, qobj, qobjarr, [cb])
+ *       - find(cname, qobj, qobjarr, hints, [cb])
  *
  * @param {String} cname Name of collection
  * @param {Object} qobj Main JSON query object
  * @param {Array} [orarr] Array of additional OR query objects (joined with OR predicate).
  * @param {Object} [hints] JSON object with query hints.
- * @param {Function} cb Callback function with arguments: (error, cursor, count) where:
+ * @param {Function} [cb] Callback function with arguments: (error, cursor, count) where:
  *          `cursor`: Cursor object to traverse records
- *          `count`:  Total number of selected records
+ *          `count`:  Total number of selected records.
+ * @return If callback is provided returns undefined.
+ *         If no callback and $onlycount hint is set returns count number.
+ *         If no callback and no $onlycount hint returns cursor object.
+ *
  */
-EJDB.prototype.find = function(cname, qobj, orarr, hints, cb) {
-    if (arguments.length == 4) {
-        cb = hints;
-        if (orarr && orarr.constructor === Array) {
-            hints = {};
-        } else {
-            hints = orarr;
-            orarr = [];
-        }
-    } else if (arguments.length == 3) {
-        cb = orarr;
-        orarr = [];
-        hints = {};
-    }
-    if (typeof cb !== "function") {
-        throw new Error("Callback 'cb' argument must be specified");
-    }
-    if (typeof cname !== "string") {
-        throw new Error("Collection name 'cname' argument must be specified");
-    }
-    if (!hints || typeof hints !== "object") {
-        hints = {};
-    }
-    if (!qobj || typeof qobj !== "object") {
-        qobj = {};
-    }
-    return this._impl.query(cname,
-            [qobj].concat(orarr, hints),
-            (hints["$onlycount"] ? ejdblib.JBQRYCOUNT : 0),
-            cb);
+EJDB.prototype.find = function() {
+    //[cname, qobj, orarr, hints, cb]
+    var qa = parseQueryArgs(Array.prototype.slice.call(arguments));
+    return this._impl.query(qa[0], [qa[1]].concat(qa[2], qa[3]),
+            (qa[3]["$onlycount"] ? ejdblib.JBQRYCOUNT : 0),
+            qa[4]);
 };
 
 /**
  * Same as #find() but retrieves only one matching JSON object.
+ * If callback is not provided this function will be synchronous.
  *
  * Call variations of findOne():
- *       - findOne(cname, qobj, cb)
- *       - findOne(cname, qobj, hints, cb)
- *       - findOne(cname, qobj, qobjarr, cb)
- *       - findOne(cname, qobj, qobjarr, hints, cb)
+ *       - findOne(cname, qobj, [cb])
+ *       - findOne(cname, qobj, hints, [cb])
+ *       - findOne(cname, qobj, qobjarr, [cb])
+ *       - findOne(cname, qobj, qobjarr, hints, [cb])
  *
  * @param {String} cname Name of collection
  * @param {Object} qobj Main JSON query object
  * @param {Array} [orarr] Array of additional OR query objects (joined with OR predicate).
  * @param {Object} [hints] JSON object with query hints.
- * @param {Function} cb Callback function with arguments: (error, obj) where:
+ * @param {Function} [cb] Callback function with arguments: (error, obj) where:
  *          `obj`:  Retrieved JSON object or NULL if it is not found.
+ * @return  If callback is provided returns undefined.
+ *          If no callback is provided returns found object or null.
  */
 
-EJDB.prototype.findOne = function(cname, qobj, orarr, hints, cb) {
-    if (arguments.length == 4) {
-        cb = hints;
-        if (orarr && orarr.constructor === Array) {
-            hints = {};
-        } else {
-            hints = orarr;
-            orarr = [];
+EJDB.prototype.findOne = function() {
+    //[cname, qobj, orarr, hints, cb]
+    var qa = parseQueryArgs(Array.prototype.slice.call(arguments));
+    qa[3]["$max"] = 1;
+    var cb = qa[4];
+    if (cb) {
+        return this._impl.query(qa[0], [qa[1]].concat(qa[2], qa[3]), 0,
+                function(err, cursor) {
+                    if (err) {
+                        cb(err);
+                        return;
+                    }
+                    if (cursor.next()) {
+                        try {
+                            cb(null, cursor.object());
+                        } finally {
+                            cursor.close();
+                        }
+                    } else {
+                        cb(null, null);
+                    }
+                });
+    } else {
+        var cursor = this._impl.query(qa[0], [qa[1]].concat(qa[2], qa[3]), 0, cb);
+        var ret = null;
+        if (cursor.next()) {
+            ret = cursor.object();
         }
-    } else if (arguments.length == 3) {
-        cb = orarr;
-        orarr = [];
-        hints = {};
-    }
-    if (typeof cb !== "function") {
-        throw new Error("Callback 'cb' argument must be specified");
+        cursor.close();
+        return ret;
     }
-    if (typeof cname !== "string") {
-        throw new Error("Collection name 'cname' argument must be specified");
-    }
-    if (!hints || typeof hints !== "object") {
-        hints = {};
-    }
-    if (!qobj || typeof qobj !== "object") {
-        qobj = {};
-    }
-    hints["$max"] = 1;
-    return this._impl.query(cname, [qobj].concat(orarr, hints), 0,
-            function(err, cursor) {
-                if (err) {
-                    cb(err);
-                    return;
-                }
-                if (cursor.next()) {
-                    try {
-                        cb(null, cursor.object());
-                    } finally {
-                        cursor.close();
-                    }
-                } else {
-                    cb(null, null);
-                }
-            });
 };
 
 
 /**
  * Convenient method to execute update queries.
- * The `$set` and `$inc` operations are supported.
+ * If callback is not provided this function will be synchronous.
  *
- * `$set` Field set operation:
- *    - {some fields for selection, '$set' : {'field1' : {obj}, ...,  'field1' : {obj}}}
- * `$inc` Increment operation. Only number types are supported.
- *    - {some fields for selection, '$inc' : {'field1' : number, ...,  'field1' : {number}}
+ * The following update operations are supported:
+ *      $set Field set operation.
+ *          - {.., '$set' : {'field1' : val1, 'fieldN' : valN}}
+ *      $inc Increment operation. Only number types are supported.
+ *          - {.., '$inc' : {'field1' : number, ...,  'field1' : number}
+ *      $dropall In-place record removal operation.
+ *          - {.., '$dropall' : true}
+ *      $addToSet Atomically adds value to the array only if its not in the array already.
+ *                  If containing array is missing it will be created.
+ *          - {.., '$addToSet' : {'json.field.path' : val1, 'json.field.pathN' : valN, ...}}
+ *      $pull Atomically removes all occurrences of value from field, if field is an array.
+ *          - {.., '$pull' : {'json.field.path' : val1, 'json.field.pathN' : valN, ...}}
  *
  * Call variations of update():
- *    update(cname, qobj, cb)
- *    update(cname, qobj, hints, cb)
- *    update(cname, qobj, qobjarr, cb)
- *    update(cname, qobj, qobjarr, hints, cb)
+ *    update(cname, qobj, [cb])
+ *    update(cname, qobj, hints, [cb])
+ *    update(cname, qobj, qobjarr, [cb])
+ *    update(cname, qobj, qobjarr, hints, [cb])
  *
  * @param {String} cname Name of collection
  * @param {Object} qobj Main JSON query object
  * @param {Array} [orarr] Array of additional OR query objects (joined with OR predicate).
  * @param {Object} [hints] JSON object with query hints.
- * @param {Function} cb Callback function with arguments: (error, count) where:
+ * @param {Function} [cb] Callback function with arguments: (error, count) where:
  *          `count`:  The number of updated records.
+ *
+ * @return  If callback is provided returns undefined.
+ *          If no callback is provided returns count of updated object.
  */
-EJDB.prototype.update = function(cname, qobj, orarr, hints, cb) {
-    if (arguments.length == 4) {
-        cb = hints;
-        if (orarr && orarr.constructor === Array) {
-            hints = {};
-        } else {
-            hints = orarr;
-            orarr = [];
-        }
-    } else if (arguments.length == 3) {
-        cb = orarr;
-        orarr = [];
-        hints = {};
-    }
-    if (typeof cb !== "function") {
-        cb = null;
-    }
-    if (typeof cname !== "string") {
-        throw new Error("Collection name 'cname' argument must be specified");
-    }
-    if (!hints || typeof hints !== "object") {
-        hints = {};
-    }
-    if (!qobj || typeof qobj !== "object") {
-        qobj = {};
+EJDB.prototype.update = function() {
+    //[cname, qobj, orarr, hints, cb]
+    var qa = parseQueryArgs(Array.prototype.slice.call(arguments));
+    var cb = qa[4];
+    if (cb) {
+        return this._impl.query(qa[0], [qa[1]].concat(qa[2], qa[3]), ejdblib.JBQRYCOUNT,
+                function(err, cursor, count, log) {
+                    if (err) {
+                        cb(err, null, log);
+                        return;
+                    }
+                    cb(null, count, log);
+                });
+    } else {
+        return this._impl.query(qa[0], [qa[1]].concat(qa[2], qa[3]), ejdblib.JBQRYCOUNT, cb);
     }
-    return this._impl.query(cname,
-            [qobj].concat(orarr, hints),
-            ejdblib.JBQRYCOUNT,
-            function(err, cursor, count, log) {
-                if (err) {
-                    cb(err, null, log);
-                    return;
-                }
-                cb(null, count, log);
-            });
 };
 
 /**
  * Convenient count(*) operation.
  *
  * Call variations of count():
- *       - count(cname, qobj, cb)
- *       - count(cname, qobj, hints, cb)
- *       - count(cname, qobj, qobjarr, cb)
- *       - count(cname, qobj, qobjarr, hints, cb)
+ *       - count(cname, qobj, [cb])
+ *       - count(cname, qobj, hints, [cb])
+ *       - count(cname, qobj, qobjarr, [cb])
+ *       - count(cname, qobj, qobjarr, hints, [cb])
  *
  * @param {String} cname Name of collection
  * @param {Object} qobj Main JSON query object
  * @param {Array} [orarr] Array of additional OR query objects (joined with OR predicate).
  * @param {Object} [hints] JSON object with query hints.
- * @param {Function} cb Callback function with arguments: (error, count) where:
+ * @param {Function} [cb] Callback function with arguments: (error, count) where:
  *          `count`:  Number of matching records.
+ * @return  If callback is provided returns undefined.
+ *          If no callback is provided returns count of updated object.
  */
-EJDB.prototype.count = function(cname, qobj, orarr, hints, cb) {
-    if (arguments.length == 4) {
-        cb = hints;
-        if (orarr && orarr.constructor === Array) {
-            hints = {};
-        } else {
-            hints = orarr;
-            orarr = [];
-        }
-    } else if (arguments.length == 3) {
-        cb = orarr;
-        orarr = [];
-        hints = {};
-    }
-    if (typeof cb !== "function") {
-        throw new Error("Callback 'cb' argument must be specified");
-    }
-    if (typeof cname !== "string") {
-        throw new Error("Collection name 'cname' argument must be specified");
-    }
-    if (!hints || typeof hints !== "object") {
-        hints = {};
-    }
-    if (!qobj || typeof qobj !== "object") {
-        qobj = {};
+EJDB.prototype.count = function() {
+    //[cname, qobj, orarr, hints, cb]
+    var qa = parseQueryArgs(Array.prototype.slice.call(arguments));
+    var cb = qa[4];
+    if (cb) {
+        return this._impl.query(qa[0], [qa[1]].concat(qa[2], qa[3]), ejdblib.JBQRYCOUNT,
+                function(err, cursor, count, log) {
+                    if (err) {
+                        cb(err, null, log);
+                        return;
+                    }
+                    cb(null, count, log);
+                });
+    } else {
+        return this._impl.query(qa[0], [qa[1]].concat(qa[2], qa[3]), ejdblib.JBQRYCOUNT, cb);
     }
-    return this._impl.query(cname,
-            [qobj].concat(orarr, hints),
-            ejdblib.JBQRYCOUNT,
-            function(err, cursor, count) {
-                if (err) {
-                    cb(err);
-                    return;
-                }
-                cursor.close();
-                cb(null, count);
-            });
 };
 
-
 /**
  * Synchronize entire EJDB database and
  * all its collections with storage.
index b8d1d71..10cc87e 100644 (file)
@@ -379,7 +379,7 @@ namespace ejdb {
         V8ObjSet::iterator it = ctx->tset.find(obj);
         if (it != ctx->tset.end()) {
             bs->err = BSON_ERROR_ANY;
-            bs->errstr = strdup("Circular object reference");
+            bs->errstr = strdup("Converting circular structure to JSON");
             return;
         }
         ctx->nlevel++;
@@ -611,19 +611,26 @@ namespace ejdb {
             REQ_ARGS(3);
             REQ_STR_ARG(0, cname); //Collection name
             REQ_STR_ARG(1, soid); //String OID
-            REQ_FUN_ARG(2, cb); //Callback
-            if (soid.length() != 24) {
+            if (!ejdbisvalidoidstr(*soid)) {
                 return scope.Close(ThrowException(Exception::Error(String::New("Argument 2: Invalid OID string"))));
             }
+            Local<Function> cb;
             bson_oid_t oid;
             bson_oid_from_string(&oid, *soid);
             BSONCmdData *cmdata = new BSONCmdData(*cname);
             cmdata->ref = oid;
 
             NodeEJDB *njb = ObjectWrap::Unwrap< NodeEJDB > (args.This());
-            BSONCmdTask *task = new BSONCmdTask(cb, njb, cmdLoad, cmdata, BSONCmdTask::delete_val);
-            uv_queue_work(uv_default_loop(), &task->uv_work, s_exec_cmd_eio, s_exec_cmd_eio_after);
-            return scope.Close(args.This());
+            if (args[2]->IsFunction()) {
+                cb = Local<Function>::Cast(args[2]);
+                BSONCmdTask *task = new BSONCmdTask(cb, njb, cmdLoad, cmdata, BSONCmdTask::delete_val);
+                uv_queue_work(uv_default_loop(), &task->uv_work, s_exec_cmd_eio, s_exec_cmd_eio_after);
+                return scope.Close(args.This());
+            } else {
+                BSONCmdTask task(cb, njb, cmdLoad, cmdata, BSONCmdTask::delete_val);
+                njb->load(&task);
+                return scope.Close(njb->load_after(&task));
+            }
         }
 
         static Handle<Value> s_remove(const Arguments& args) {
@@ -631,29 +638,36 @@ namespace ejdb {
             REQ_ARGS(3);
             REQ_STR_ARG(0, cname); //Collection name
             REQ_STR_ARG(1, soid); //String OID
-            REQ_FUN_ARG(2, cb); //Callback
-            if (soid.length() != 24) {
+            if (!ejdbisvalidoidstr(*soid)) {
                 return scope.Close(ThrowException(Exception::Error(String::New("Argument 2: Invalid OID string"))));
             }
+            Local<Function> cb;
             bson_oid_t oid;
             bson_oid_from_string(&oid, *soid);
             BSONCmdData *cmdata = new BSONCmdData(*cname);
             cmdata->ref = oid;
 
             NodeEJDB *njb = ObjectWrap::Unwrap< NodeEJDB > (args.This());
-            BSONCmdTask *task = new BSONCmdTask(cb, njb, cmdRemove, cmdata, BSONCmdTask::delete_val);
-            uv_queue_work(uv_default_loop(), &task->uv_work, s_exec_cmd_eio, s_exec_cmd_eio_after);
-            return scope.Close(args.This());
+            if (args[2]->IsFunction()) {
+                cb = Local<Function>::Cast(args[2]);
+                BSONCmdTask *task = new BSONCmdTask(cb, njb, cmdRemove, cmdata, BSONCmdTask::delete_val);
+                uv_queue_work(uv_default_loop(), &task->uv_work, s_exec_cmd_eio, s_exec_cmd_eio_after);
+                return scope.Close(args.This());
+            } else {
+                BSONCmdTask task(cb, njb, cmdRemove, cmdata, BSONCmdTask::delete_val);
+                njb->remove(&task);
+                return scope.Close(njb->remove_after(&task));
+            }
         }
 
         static Handle<Value> s_save(const Arguments& args) {
             HandleScope scope;
-            REQ_ARGS(4);
+            REQ_ARGS(3);
             REQ_STR_ARG(0, cname); //Collection name
             REQ_ARR_ARG(1, oarr); //Array of JSON objects
             REQ_OBJ_ARG(2, opts); //Options obj
-            REQ_FUN_ARG(3, cb); //Callback
 
+            Local<Function> cb;
             BSONCmdData *cmdata = new BSONCmdData(*cname);
             for (uint32_t i = 0; i < oarr->Length(); ++i) {
                 Local<Value> v = oarr->Get(i);
@@ -678,9 +692,17 @@ namespace ejdb {
                 cmdata->merge = true;
             }
             NodeEJDB *njb = ObjectWrap::Unwrap< NodeEJDB > (args.This());
-            BSONCmdTask *task = new BSONCmdTask(cb, njb, cmdSave, cmdata, BSONCmdTask::delete_val);
-            uv_queue_work(uv_default_loop(), &task->uv_work, s_exec_cmd_eio, s_exec_cmd_eio_after);
-            return scope.Close(args.This());
+
+            if (args[3]->IsFunction()) { //callback provided
+                cb = Local<Function>::Cast(args[3]);
+                BSONCmdTask *task = new BSONCmdTask(cb, njb, cmdSave, cmdata, BSONCmdTask::delete_val);
+                uv_queue_work(uv_default_loop(), &task->uv_work, s_exec_cmd_eio, s_exec_cmd_eio_after);
+                return scope.Close(args.This());
+            } else {
+                BSONCmdTask task(cb, njb, cmdSave, cmdata, BSONCmdTask::delete_val);
+                njb->save(&task);
+                return scope.Close(njb->save_after(&task));
+            }
         }
 
         static Handle<Value> s_query(const Arguments& args) {
@@ -1067,7 +1089,7 @@ namespace ejdb {
             }
         }
 
-        void remove_after(BSONCmdTask *task) {
+        Handle<Value> remove_after(BSONCmdTask *task) {
             HandleScope scope;
             Local<Value> argv[1];
             if (task->cmd_ret != 0) {
@@ -1075,10 +1097,18 @@ namespace ejdb {
             } else {
                 argv[0] = Local<Primitive>::New(Null());
             }
-            TryCatch try_catch;
-            task->cb->Call(Context::GetCurrent()->Global(), 1, argv);
-            if (try_catch.HasCaught()) {
-                FatalException(try_catch);
+            if (task->cb.IsEmpty() || task->cb->IsNull() || task->cb->IsUndefined()) {
+                if (task->cmd_ret != 0)
+                    return scope.Close(ThrowException(argv[0]));
+                else
+                    return scope.Close(Undefined());
+            } else {
+                TryCatch try_catch;
+                task->cb->Call(Context::GetCurrent()->Global(), 1, argv);
+                if (try_catch.HasCaught()) {
+                    FatalException(try_catch);
+                }
+                return scope.Close(Undefined());
             }
         }
 
@@ -1113,7 +1143,7 @@ namespace ejdb {
             }
         }
 
-        void save_after(BSONCmdTask *task) {
+        Handle<Value> save_after(BSONCmdTask *task) {
             HandleScope scope;
             Local<Value> argv[2];
             if (task->cmd_ret != 0) {
@@ -1135,10 +1165,15 @@ namespace ejdb {
                 }
             }
             argv[1] = oids;
-            TryCatch try_catch;
-            task->cb->Call(Context::GetCurrent()->Global(), 2, argv);
-            if (try_catch.HasCaught()) {
-                FatalException(try_catch);
+            if (task->cb.IsEmpty() || task->cb->IsNull() || task->cb->IsUndefined()) {
+                return (task->cmd_ret != 0) ? scope.Close(ThrowException(argv[0])) : scope.Close(argv[1]);
+            } else {
+                TryCatch try_catch;
+                task->cb->Call(Context::GetCurrent()->Global(), 2, argv);
+                if (try_catch.HasCaught()) {
+                    FatalException(try_catch);
+                }
+                return scope.Close(Undefined());
             }
         }
 
@@ -1157,7 +1192,7 @@ namespace ejdb {
             cmdata->bsons.push_back(ejdbloadbson(coll, &task->cmd_data->ref));
         }
 
-        void load_after(BSONCmdTask *task) {
+        Handle<Value> load_after(BSONCmdTask *task) {
             HandleScope scope;
             Local<Value> argv[2];
             if (task->cmd_ret != 0) {
@@ -1175,10 +1210,15 @@ namespace ejdb {
             } else {
                 argv[1] = Local<Primitive>::New(Null());
             }
-            TryCatch try_catch;
-            task->cb->Call(Context::GetCurrent()->Global(), 2, argv);
-            if (try_catch.HasCaught()) {
-                FatalException(try_catch);
+            if (task->cb.IsEmpty() || task->cb->IsNull() || task->cb->IsUndefined()) {
+                return (task->cmd_ret != 0) ? scope.Close(ThrowException(argv[0])) : scope.Close(argv[1]);
+            } else {
+                TryCatch try_catch;
+                task->cb->Call(Context::GetCurrent()->Global(), 2, argv);
+                if (try_catch.HasCaught()) {
+                    FatalException(try_catch);
+                }
+                return scope.Close(Undefined());
             }
         }
 
index 304af66..dd0e2c8 100644 (file)
@@ -169,7 +169,7 @@ module.exports.testCircular = function(test) {
         err = e;
     }
     test.ok(err);
-    test.equal(err.message, "Circular object reference");
+    test.equal(err.message, "Converting circular structure to JSON");
 
     err = null;
     try {
@@ -178,7 +178,7 @@ module.exports.testCircular = function(test) {
         err = e;
     }
     test.ok(err);
-    test.equal(err.message, "Circular object reference");
+    test.equal(err.message, "Converting circular structure to JSON");
     test.done();
 };
 
index d715e00..3f8b2b1 100644 (file)
@@ -19,6 +19,9 @@
         "test" : "make -f tests.mk check-all",
         "postinstall" : "make -f tests.mk check"
     },
+    "bin" : {
+        "ejdb" : "./bin/cli.js"
+    },
     "author" : {
         "name" : "Anton Adamansky",
         "email" : "adamansky@gmail.com"