From 87a9cd52a62bbf2725a037d0460f75eaef3431b9 Mon Sep 17 00:00:00 2001
From: Xavier Lacot <xavier@lacot.org>
Date: Mon, 28 Nov 2011 17:37:04 +0100
Subject: [PATCH] turned out joli.js as a CommonJS module, while still
 maintaining the backward compatibility + updated the doc. Thanks to
 mfferreira and nicjansma

---
 LICENSE         |    2 +-
 README.markdown |   24 +-
 joli.js         | 1403 +++++++++++++++++++++++------------------------
 3 files changed, 706 insertions(+), 723 deletions(-)

diff --git a/LICENSE b/LICENSE
index 736f6b3..cce6eba 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2010 Xavier Lacot
+Copyright (c) 2010-2011 Xavier Lacot
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/README.markdown b/README.markdown
index 08b14af..f73bd0f 100644
--- a/README.markdown
+++ b/README.markdown
@@ -9,18 +9,28 @@ joli.js is widely unit-tested. Go check [the demo application](https://github.co
 "joli" means in French "nice", "tiny". Just what joli.js tries to be.
 
 ## Download and install
-Just grab joli.js (a single file), and include it in your Titanium project using
+The source code of joli.js is [available on GitHub](https://github.com/xavierlacot/joli.js). Just grab it and include it in your Titanium project using either `Titanium.include()` or the CommonJS `require()`statement:
 
     Titanium.include('joli.js');
+    
+or (please note the missing ".js" suffix):
 
-The sources are [available on GitHub](https://github.com/xavierlacot/joli.js).
+    var joli = require('path/to/joli').connect('your_database_name');
+
+The latter integration mode must be prefered, as it helps sandboxing external libraries. Loading joli.js with `require()` will also allow to use several databases in the same application, which is not possible with `Ti.include()`:
+
+    var joliLibrary = require('path/to/joli');
+    var database1 = joliLibrary.connect('first_database');
+    var database2 = joliLibrary.connect('second_database');
 
 ## Configuration
 
 ### Database connection creation
-There is one single required step in the configuration of joli.js: configuring the database name. This can be done in only one line, which has to be put before every call to joli.js's API:
+If you included joli.js with `Titanium.include()`, there is one single required configuration step: configuring the database name. This can be done in only one line, which has to be put before every call to joli.js's API:
 
     joli.connection = new joli.Connection('your_database_name');
+    
+If you prefered to load joli.js as a CommonJS module, it is not necessary to write this configuration instruction. However, you still may want to change the database name of a connection, and in that case you'll want to use this command.
 
 ### Models configuration
 Prior inserting data and querying your models, you must declare these models. This is done by instantiating the class "joli.model":
@@ -252,6 +262,13 @@ There are at the moment three open source applications using joli.js:
 * [joli.js-demo](https://github.com/xavierlacot/joli.js-demo/) is a demo application, which contains the unit-tests for joli.js;
 * [joli.api.js-demo](https://github.com/xavierlacot/joli.api.js-demo/) is an Iphone Addressbook-like application, which content gets synchronized with web services. This application was built in a couple of hours and was presented a a demo app at [CodeStrong](http://codestrong.com/) in 2011.
 
+### Use several databases in the same application
+It is possible to use several databases with joli.js by loading the library as a CommonJS module:
+
+    var joliLibrary = require('path/to/joli');
+    var database1 = joliLibrary.connect('first_database');
+    var database2 = joliLibrary.connect('second_database');
+
 ## Credits and support
 joli.js has been developed by [Xavier Lacot](http://lacot.org/) and is
 licensed under the MIT license.
@@ -261,6 +278,7 @@ Please use GitHub in order to report bugs, but you may also ask for help on how
 ## Changelog
 
 ### master
+* turned joli.js as a commonjs module
 * added a .as() method for building queries with join() (thanks nicjansma)
 * fixed a bug in the query where() method, when a value was 0 or '' (thanks nicjansma)
 * added a toArray() method on record instances
diff --git a/joli.js b/joli.js
index 6b7c57c..eef5129 100644
--- a/joli.js
+++ b/joli.js
@@ -1,854 +1,819 @@
 var joli = {
-  each: function(collection, iterator, bind) {
-    var i, l, property;
-
-    switch (joli.getType(collection)) {
-      case "array":
-        for (i = 0, l = collection.length; i < l; i++) {
-          iterator.call(bind, collection[i], i);
-        }
-        break;
-      case "object":
-        for (property in collection) {
-          if (collection.hasOwnProperty(property)) {
-            iterator.call(bind, collection[property], property);
-          }
-        }
-        break;
-    }
-  },
-
-  extend: function(baseClass, options) {
-    var opt, prop;
-
-    if (!this.options) {
-      this.options = {};
-    }
-
-    this.parent = new baseClass(options);
-
-    for (prop in this.parent) {
-      this[prop] = this[prop] || this.parent[prop];
-    }
-
-    // copy base options over
-    for (opt in this.parent.options) {
-      this.options[opt] = this.options[opt] || this.parent.options[opt];
-    }
-  },
-
-  getType: function(obj) {
-    if (typeof obj === "undefined" || obj === null || (typeof obj === "number" && isNaN(obj))) {
-      return false;
-    } else if (obj.constructor === Array) {
-      return "array";
-    } else {
-      return typeof obj;
-    }
-  },
-
-  jsonParse: function(json) {
-    return JSON.parse(json);
-  },
-
-  merge: function() {
-    var i, l, prop;
-
-    var mergedObject = {};
+    each: function(collection, iterator, bind) {
+        var i, l, property;
+
+        switch (joli.getType(collection)) {
+            case "array":
+                for ( i = 0, l = collection.length; i < l; i++) {
+                    iterator.call(bind, collection[i], i);
+                }
+                break;
+            case "object":
+                for (property in collection) {
+                    if (collection.hasOwnProperty(property)) {
+                        iterator.call(bind, collection[property], property);
+                    }
+                }
+                break;
+        }
+    },
+    extend: function(baseClass, options) {
+        var opt, prop;
 
-    for (i = 0, l = arguments.length; i < l; i++) {
-      var object = arguments[i];
+        if (!this.options) {
+            this.options = {};
+        }
 
-      if (joli.getType(object) !== "object") {
-        continue;
-      }
+        this.parent = new baseClass(options);
 
-      for (prop in object) {
-        if (object.hasOwnProperty(prop)) {
-          var objectProp = object[prop], mergedProp = mergedObject[prop];
+        for (prop in this.parent) {
+            this[prop] = this[prop] || this.parent[prop];
+        }
 
-          if (mergedProp && joli.getType(objectProp) === "object" && joli.getType(mergedProp) === "object") {
-            mergedObject[prop] = joli.merge(mergedProp, objectProp);
-          } else {
-            mergedObject[prop] = objectProp;
-          }
+        // copy base options over
+        for (opt in this.parent.options) {
+            this.options[opt] = this.options[opt] || this.parent.options[opt];
         }
-      }
-    }
-    return mergedObject;
-  },
+    },
+    getType: function(obj) {
+        if (typeof obj === "undefined" || obj === null || (typeof obj === "number" && isNaN(obj))) {
+            return false;
+        } else if (obj.constructor === Array) {
+            return "array";
+        } else {
+            return typeof obj;
+        }
+    },
+    jsonParse: function(json) {
+        return JSON.parse(json);
+    },
+    merge: function() {
+        var i, l, prop;
+        var mergedObject = {};
+
+        for (i = 0, l = arguments.length; i < l; i++) {
+            var object = arguments[i];
+
+            if (joli.getType(object) !== "object") {
+                continue;
+            }
 
-  setOptions: function(options, defaults) {
-    var opt;
+            for (prop in object) {
+                if (object.hasOwnProperty(prop)) {
+                    var objectProp = object[prop], mergedProp = mergedObject[prop];
 
-    if (!options) {
-      options = {};
-    }
+                    if (mergedProp && joli.getType(objectProp) === "object" && joli.getType(mergedProp) === "object") {
+                        mergedObject[prop] = joli.merge(mergedProp, objectProp);
+                    } else {
+                        mergedObject[prop] = objectProp;
+                    }
+                }
+            }
+        }
+        return mergedObject;
+    },
+    setOptions: function(options, defaults) {
+        var opt;
 
-    if (!this.options) {
-      this.options = {};
-    }
+        if (!options) {
+            options = {};
+        }
 
-    var mergedOptions = joli.merge(defaults, options);
+        if (!this.options) {
+            this.options = {};
+        }
 
-    for (opt in defaults) {
-      this.options[opt] = mergedOptions[opt];
-    }
-  },
+        var mergedOptions = joli.merge(defaults, options);
 
-  toQueryString: function(obj) {
-    var queryStringComponents = [];
+        for (opt in defaults) {
+            this.options[opt] = mergedOptions[opt];
+        }
+    },
+    toQueryString: function(obj) {
+        var queryStringComponents = [];
 
-    if (!obj) {
-      obj = {};
-    }
+        if (!obj) {
+            obj = {};
+        }
 
-    joli.each(obj, function(val, key) {
-      var result = null;
-
-      switch (joli.getType(val)) {
-        case "object":
-          result = joli.toQueryString(val);
-          break;
-        case "array":
-          result = joli.toQueryString(val);
-          break;
-        default:
-          result = encodeURIComponent(val);
-      }
-
-      if (result) {
-        queryStringComponents.push(key + '=' + result);
-      }
-    });
+        joli.each(obj, function(val, key) {
+            var result = null;
+
+            switch (joli.getType(val)) {
+                case "object":
+                    result = joli.toQueryString(val);
+                    break;
+                case "array":
+                    result = joli.toQueryString(val);
+                    break;
+                default:
+                    result = encodeURIComponent(val);
+            }
 
-    return '[' + queryStringComponents.join("&") + ']';
-  },
-
-  typeValue: function(val) {
-    if (!joli.getType(val)) {
-      return "NULL";
-    } else {
-      if (joli.getType(val) === "string") {
-        // escape single quotes and dollar signs.
-        // quotes are escaped for SQLite
-        val = val.replace(/'/g, "''");
-        // dollar signs are escaped for use in calling str.replace in JazzRecord.replaceAndClean()
-        val = val.replace(/\$/g, "$$$$");
-        val = "'" + val + "'";
-      } else if (joli.getType(val) === "boolean") {
-        if (val) {
-          return "1";
+            if (result) {
+                queryStringComponents.push(key + '=' + result);
+            }
+        });
+        return '[' + queryStringComponents.join("&") + ']';
+    },
+    typeValue: function(val) {
+        if (!joli.getType(val)) {
+            return "NULL";
         } else {
-          return "0";
+            if (joli.getType(val) === "string") {
+                // escape single quotes and dollar signs.
+                // quotes are escaped for SQLite
+                val = val.replace(/'/g, "''");
+                // dollar signs are escaped for use in calling str.replace in
+                // JazzRecord.replaceAndClean()
+                val = val.replace(/\$/g, "$$$$");
+                val = "'" + val + "'";
+            } else if (joli.getType(val) === "boolean") {
+                if (val) {
+                    return "1";
+                } else {
+                    return "0";
+                }
+            }
         }
-      }
-    }
 
-    return val;
-  }
+        return val;
+    }
 };
 
-
 /**
  * Connection
  */
 joli.Connection = function(database) {
-  this.dbname = database;
-  this.database = Titanium.Database.open(this.dbname);
-  this.database.execute('PRAGMA read_uncommitted=true');
+    this.dbname = database;
+    this.database = Titanium.Database.open(this.dbname);
+    this.database.execute('PRAGMA read_uncommitted=true');
 };
 
 joli.Connection.prototype = {
-  execute: function(query) {
-//    Titanium.API.log('debug', query);
-    return this.database.execute(query);
-  },
-
-  lastInsertRowId: function() {
-    return this.database.lastInsertRowId;
-  }
+    execute: function(query) {
+        // Titanium.API.log('debug', query);
+        return this.database.execute(query);
+    },
+    lastInsertRowId: function() {
+        return this.database.lastInsertRowId;
+    }
 };
 
-
 /**
  * Migration description
  */
 joli.migration = function(options) {
-  var defaults = {
-    tableName: 'migration'
-  };
+    var defaults = {
+        tableName: 'migration'
+    };
 
-  joli.setOptions.call(this, options, defaults);
-  this.table = this.options.tableName;
+    joli.setOptions.call(this, options, defaults);
+    this.table = this.options.tableName;
 };
 
 joli.migration.prototype = {
-  getVersion: function() {
-    var q = new joli.query().select().from(this.table).order('version desc');
-    var version = q.execute();
-
-    if (version.length > 0) {
-      return version[0].version;
-    } else {
-      q = new joli.query().insertInto(this.table).values({ version: 0 });
-      q.execute();
-      return 0;
-    }
-  },
+    getVersion: function() {
+        var q = new joli.query().select().from(this.table).order('version desc');
+        var version = q.execute();
 
-  setVersion: function(version) {
-    var q = new joli.query().update(this.table).set({ version:  version });
-    q.execute();
-  }
+        if (version.length > 0) {
+            return version[0].version;
+        } else {
+            q = new joli.query().insertInto(this.table).values({
+                version: 0
+            });
+            q.execute();
+            return 0;
+        }
+    },
+    setVersion: function(version) {
+        var q = new joli.query().update(this.table).set({
+            version: version
+        });
+        q.execute();
+    }
 };
 
 /**
  * Model description
  */
 joli.model = function(options) {
-  var defaults = {
-    table: '',
-    columns: {},
-    objectMethods: {}
-  };
-
-  if (options.methods) {
-    joli.each(options.methods, function(method, name) {
-      this[name] = method;
-    }, this);
-  }
-
-  joli.setOptions.call(this, options, defaults);
-  this.table = this.options.table;
-
-  if (!joli.models.has(this.table)) {
-    joli.models.set(this.table, this);
-  }
-};
-
-joli.model.prototype = {
-  all: function(constraints) {
-    var q = new joli.query().select().from(this.table);
-
-    if (!constraints) {
-      constraints = {};
-    }
-
-    if (constraints.where) {
-      joli.each(constraints.where, function(value, field) {
-        q.where(field, value);
-      });
-    }
-
-    if (constraints.order) {
-      q.order(constraints.order);
-    }
-
-    if (constraints.limit) {
-      q.limit(constraints.limit);
-    }
-
-    return q.execute();
-  },
-
-  count: function(constraints) {
-    var q = new joli.query().count().from(this.table);
-
-    if (!constraints) {
-      constraints = {};
-    }
+    var defaults = {
+        table: '',
+        columns: {},
+        objectMethods: {}
+    };
 
-    if (constraints.where) {
-      joli.each(constraints.where, function(value, field) {
-        q.where(field, value);
-      });
+    if (options.methods) {
+        joli.each(options.methods, function(method, name) {
+            this[name] = method;
+        }, this);
     }
 
-    return parseInt(q.execute(), 10);
-  },
+    joli.setOptions.call(this, options, defaults);
+    this.table = this.options.table;
 
-  // no callbacks, more efficient
-  deleteRecords: function(id) {
-    var q = new joli.query().destroy().from(this.table);
-
-    if (joli.getType(id) === 'number') {
-      q.where('id = ?', id);
-    } else if(joli.getType(id) === 'array') {
-      q.whereIn('id', id);
+    if (!joli.models.has(this.table)) {
+        joli.models.set(this.table, this);
     }
+};
 
-    return q.execute();
-  },
-
-  exists: function(id) {
-    var count = new joli.query().count().from(this.table).where('id = ?', id).execute();
-    return (count > 0);
-  },
+joli.model.prototype = {
+    all: function(constraints) {
+        var q = new joli.query().select().from(this.table);
 
-  findBy: function(field, value) {
-    return new joli.query().select().from(this.table).where(field + ' = ?', value).execute();
-  },
+        if (!constraints) {
+            constraints = {};
+        }
 
-  findById: function(value) {
-    return this.findBy('id', value);
-  },
+        if (constraints.where) {
+            joli.each(constraints.where, function(value, field) {
+                q.where(field, value);
+            });
+        }
 
-  findOneBy: function(field, value) {
-    var result = new joli.query().select().from(this.table).where(field + ' = ?', value).limit(1).execute();
+        if (constraints.order) {
+            q.order(constraints.order);
+        }
 
-    if (result.length === 0) {
-      return false;
-    } else {
-      return result[0];
-    }
-  },
+        if (constraints.limit) {
+            q.limit(constraints.limit);
+        }
 
-  findOneById: function(value) {
-    return this.findOneBy('id', value);
-  },
+        return q.execute();
+    },
+    count: function(constraints) {
+        var q = new joli.query().count().from(this.table);
 
-  getColumns: function() {
-    return this.options.columns;
-  },
+        if (!constraints) {
+            constraints = {};
+        }
 
-  newRecord: function(values) {
-    if (!values) {
-      values = {};
-    }
+        if (constraints.where) {
+            joli.each(constraints.where, function(value, field) {
+                q.where(field, value);
+            });
+        }
 
-    var data = {};
+        return parseInt(q.execute(), 10);
+    },
+    // no callbacks, more efficient
+    deleteRecords: function(id) {
+        var q = new joli.query().destroy().from(this.table);
 
-    joli.each(this.options.columns, function(colType, colName) {
-      data[colName] = (values[colName] === undefined) ? null : values[colName];
-    });
+        if (joli.getType(id) === 'number') {
+            q.where('id = ?', id);
+        } else if (joli.getType(id) === 'array') {
+            q.whereIn('id', id);
+        }
 
-    var record = new joli.record(this).fromArray(data);
+        return q.execute();
+    },
+    exists: function(id) {
+        var count = new joli.query().count().from(this.table).where('id = ?', id).execute();
+        return (count > 0);
+    },
+    findBy: function(field, value) {
+        return new joli.query().select().from(this.table).where(field + ' = ?', value).execute();
+    },
+    findById: function(value) {
+        return this.findBy('id', value);
+    },
+    findOneBy: function(field, value) {
+        var result = new joli.query().select().from(this.table).where(field + ' = ?', value).limit(1).execute();
+
+        if (result.length === 0) {
+            return false;
+        } else {
+            return result[0];
+        }
+    },
+    findOneById: function(value) {
+        return this.findOneBy('id', value);
+    },
+    getColumns: function() {
+        return this.options.columns;
+    },
+    newRecord: function(values) {
+        if (!values) {
+            values = {};
+        }
 
-    record.isNew = function() {
-      return true;
-    };
+        var data = {};
 
-    // add object methods
-    if (this.options.objectMethods) {
-      joli.each(this.options.objectMethods, function(method, name) {
-        record[name] = method;
-      });
-    }
+        joli.each(this.options.columns, function(colType, colName) {
+            data[colName] = (values[colName] === undefined) ? null : values[colName];
+        });
+        var record = new joli.record(this).fromArray(data);
+
+        record.isNew = function() {
+            return true;
+        };
+        // add object methods
+        if (this.options.objectMethods) {
+            joli.each(this.options.objectMethods, function(method, name) {
+                record[name] = method;
+            });
+        }
 
-    return record;
-  },
+        return record;
+    },
+    save: function(data) {
+        if (data.data.length === 0) {
+            return;
+        }
 
-  save: function(data) {
-    if (data.data.length === 0) {
-      return;
-    }
+        var q = new joli.query();
 
-    var q = new joli.query();
+        if (data.originalData) {
+            // existing record
+            q.update(this.table).set(data.data).where('id = ?', data.originalData.id);
+        } else {
+            // new record
+            q.insertInto(this.table).values(data.data);
+        }
 
-    if (data.originalData) {
-      // existing record
-      q.update(this.table).set(data.data).where('id = ?', data.originalData.id);
-    } else {
-      // new record
-      q.insertInto(this.table).values(data.data);
+        return q.execute();
+    },
+    truncate: function() {
+        new joli.query().destroy().from(this.table).execute();
     }
-
-    return q.execute();
-  },
-
-  truncate: function() {
-    new joli.query().destroy().from(this.table).execute();
-  }
 };
 
 joli.Models = function() {
-  this.models = {};
-  this.migration = new joli.migration({ tableName: 'migration' });
+    this.models = {};
+    this.migration = new joli.migration({
+        tableName: 'migration'
+    });
 };
 
 joli.Models.prototype = {
-  get: function(table) {
-    return this.models[table];
-  },
-
-  has: function(table) {
-    if (this.models[table]) {
-      return true;
-    } else {
-      return false;
-    }
-  },
-
-  initialize: function() {
-    joli.each(this.models, function(model, modelName) {
-      var columns = [];
-
-      joli.each(model.options.columns, function(type, name) {
-        columns.push(name + ' ' + type);
-      });
-      var query = 'CREATE TABLE IF NOT EXISTS ' + modelName + ' (' + columns.join(', ') + ')';
-      joli.connection.execute(query);
-    });
-  },
-
-  migrate: function(version, migrationCallback) {
-    // create migration table
-    var query = 'CREATE TABLE IF NOT EXISTS ' + this.migration.table + ' (version)';
-    joli.connection.execute(query);
-
-    if (this.migration.getVersion() < version) {
-      joli.each(this.models, function(model, modelName) {
-        var query = 'DROP TABLE IF EXISTS ' + modelName;
+    get: function(table) {
+        return this.models[table];
+    },
+    has: function(table) {
+        if (this.models[table]) {
+            return true;
+        } else {
+            return false;
+        }
+    },
+    initialize: function() {
+        joli.each(this.models, function(model, modelName) {
+            var columns = [];
+
+            joli.each(model.options.columns, function(type, name) {
+                columns.push(name + ' ' + type);
+            });
+            var query = 'CREATE TABLE IF NOT EXISTS ' + modelName + ' (' + columns.join(', ') + ')';
+            joli.connection.execute(query);
+        });
+    },
+    migrate: function(version, migrationCallback) {
+        // create migration table
+        var query = 'CREATE TABLE IF NOT EXISTS ' + this.migration.table + ' (version)';
         joli.connection.execute(query);
-      });
 
-      // optional migration callback
-      if (migrationCallback && 'function' == joli.getType(migrationCallback)) {
-        migrationCallback({
-          table:      this.migration.table,
-          newVersion: version
-        });
-      }
+        if (this.migration.getVersion() < version) {
+            joli.each(this.models, function(model, modelName) {
+                var query = 'DROP TABLE IF EXISTS ' + modelName;
+                joli.connection.execute(query);
+            });
+            // optional migration callback
+            if (migrationCallback && 'function' === joli.getType(migrationCallback)) {
+                migrationCallback({
+                    table: this.migration.table,
+                    newVersion: version
+                });
+            }
 
-      // insert migration
-      this.migration.setVersion(version);
+            // insert migration
+            this.migration.setVersion(version);
+        }
+    },
+    set: function(table, model) {
+        this.models[table] = model;
     }
-  },
-
-  set: function(table, model) {
-    this.models[table] = model;
-  }
 };
 
 joli.models = new joli.Models();
 
 joli.query = function() {
-  this.data = {
-    from:            null,
-    join:            [],
-    limit:           null,
-    operation:       null,
-    order:           [],
-    select_columns:  '*',
-    set:             [],
-    values:          [],
-    where:           null,
-    as:              null
-  };
+    this.data = {
+        as: null,
+        from: null,
+        join: [],
+        limit: null,
+        operation: null,
+        order: [],
+        select_columns: '*',
+        set: [],
+        values: [],
+        where: null
+    };
 };
 
 joli.query.prototype = {
-  count: function() {
-    this.data.operation = 'count';
-    return this;
-  },
-
-  destroy: function() {
-    this.data.operation = 'delete';
-    return this;
-  },
-
-  execute: function(hydratationMode) {
-    return this.executeQuery(this.getQuery(), hydratationMode);
-  },
-
-  executeQuery: function(query, hydratationMode) {
-    var rows;
-
-    switch (this.data.operation) {
-      case 'count':
-        rows = joli.connection.execute(query);
-        return this.getCount(rows);
-      case 'insert_into':
-        joli.connection.execute(query);
-        return joli.connection.lastInsertRowId();
-      case 'select':
-        if (typeof hydratationMode === 'undefined') {
-          hydratationMode = 'object';
+    count: function() {
+        this.data.operation = 'count';
+        return this;
+    },
+    destroy: function() {
+        this.data.operation = 'delete';
+        return this;
+    },
+    execute: function(hydratationMode) {
+        return this.executeQuery(this.getQuery(), hydratationMode);
+    },
+    executeQuery: function(query, hydratationMode) {
+        var rows;
+
+        switch (this.data.operation) {
+            case 'count':
+                rows = joli.connection.execute(query);
+                return this.getCount(rows);
+            case 'insert_into':
+                joli.connection.execute(query);
+                return joli.connection.lastInsertRowId();
+            case 'select':
+                if (typeof hydratationMode === 'undefined') {
+                    hydratationMode = 'object';
+                }
+                rows = joli.connection.execute(query);
+                return this.hydrate(rows, hydratationMode);
+            default:
+                return joli.connection.execute(query);
         }
+    },
+    from: function(table) {
+        this.data.from = table;
+        return this;
+    },
+    as: function(table) {
+        this.data.as = table;
+        return this;
+    },
+    getCount: function(rows) {
+        var result;
 
-        rows = joli.connection.execute(query);
-        return this.hydrate(rows, hydratationMode);
-      default:
-        return joli.connection.execute(query);
-    }
-  },
-
-  from: function(table) {
-    this.data.from = table;
-    return this;
-  },
-
-  as: function(table) {
-    this.data.as = table;
-    return this;
-  },
-
-  getCount: function(rows) {
-    var result;
-
-    if (null === rows) {
-      return 0;
-    }
-
-    if (0 === rows.rowCount) {
-      result = 0;
-    } else {
-      result = rows.fieldByName('total');
-    }
-
-    rows.close();
-    return result;
-  },
-
-  getOperation: function() {
-    switch (this.data.operation) {
-      case 'count':
-        return 'select count(*) as total from ' + this.data.from;
-      case 'delete':
-        return 'delete from ' + this.data.from;
-      case 'insert_into':
-        return 'insert into ' + this.data.from + ' (' + this.data.set.join(', ') + ') values (' + this.data.values.join(', ') + ')';
-      case 'select':
-        var join = '';
-
-        if (this.data.join.length > 0) {
-          joli.each(this.data.join, function(value, key) {
-            if (-1 === value[1].indexOf('.')) {
-              value[1] = value[0] + '.' + value[1];
-            }
-
-            join = join + ' left outer join ' + value[0] + ' on ' + value[1] + ' = ' + value[2];
-          });
+        if (null === rows) {
+            return 0;
         }
 
-        return 'select ' + this.data.select_columns + ' from ' + this.data.from + join;
-      case 'update':
-        return 'update ' + this.data.from + ' set ' + this.data.set.join(', ');
-      default:
-        throw("Operation type Error. joli.query operation type must be an insert, a delete, a select or an update.");
-    }
-  },
-
-  getQuery: function() {
-    var query = this.getOperation();
-
-    if (this.data.where) {
-      query += ' where ' + this.data.where;
-    }
-
-    if (this.data.groupBy) {
-      query += ' group by ' + this.data.groupBy.join(', ');
-    }
-
-    if (this.data.order.length > 0) {
-      query += ' order by ' + this.data.order.join(', ');
-    }
-
-    if (this.data.limit) {
-      query += ' limit ' + this.data.limit;
-    }
-
-    return query;
-  },
-
-  groupBy: function(group) {
-    if ('string' == joli.getType(group)) {
-      group = [group];
-    }
-
-    this.data.groupBy = group;
-    return this;
-  },
-
-  hydrate: function(rows, hydratationMode) {
-    var result = [];
-
-    if (null === hydratationMode) {
-      hydratationMode = 'object';
-    }
-
-    if (!rows) {
-      return result;
-    }
+        if (0 === rows.rowCount) {
+            result = 0;
+        } else {
+            result = rows.fieldByName('total');
+        }
 
-    var fieldCount;
+        rows.close();
+        return result;
+    },
+    getOperation: function() {
+        switch (this.data.operation) {
+            case 'count':
+                return 'select count(*) as total from ' + this.data.from;
+            case 'delete':
+                return 'delete from ' + this.data.from;
+            case 'insert_into':
+                return 'insert into ' + this.data.from + ' (' + this.data.set.join(', ') + ') values (' + this.data.values.join(', ') + ')';
+            case 'select':
+                var join = '';
+
+                if (this.data.join.length > 0) {
+                    joli.each(this.data.join, function(value, key) {
+                        if (-1 === value[1].indexOf('.')) {
+                            value[1] = value[0] + '.' + value[1];
+                        }
+                        join = join + ' left outer join ' + value[0] + ' on ' + value[1] + ' = ' + value[2];
+                    });
+                }
+
+                return 'select ' + this.data.select_columns + ' from ' + this.data.from + join;
+            case 'update':
+                return 'update ' + this.data.from + ' set ' + this.data.set.join(', ');
+            default:
+                throw ("Operation type Error. joli.query operation type must be an insert, a delete, a select or an update.");
+        }
+    },
+    getQuery: function() {
+        var query = this.getOperation();
 
-    if (Titanium.Platform.name != 'android') {
-      fieldCount = rows.fieldCount();
-    } else {
-      fieldCount = rows.fieldCount;
-    }
+        if (this.data.where) {
+            query += ' where ' + this.data.where;
+        }
 
-    switch (hydratationMode) {
-      case 'array':
-        result = this.hydrateArray(rows, fieldCount);
-        break;
-      case 'object':
-        result = this.hydrateObject(rows, fieldCount);
-        break;
-      default:
-        throw('Unknown hydratation mode "' + hydratationMode + '". hydratationMode must be "object" or "array"');
-    }
+        if (this.data.groupBy) {
+            query += ' group by ' + this.data.groupBy.join(', ');
+        }
 
-    rows.close();
-    return result;
-  },
+        if (this.data.order.length > 0) {
+            query += ' order by ' + this.data.order.join(', ');
+        }
 
-  hydrateArray: function(rows, fieldCount) {
-    var result = [];
-    var i;
-    var rowData;
+        if (this.data.limit) {
+            query += ' limit ' + this.data.limit;
+        }
 
-    while (rows.isValidRow()) {
-      i = 0;
-      rowData = {};
+        return query;
+    },
+    groupBy: function(group) {
+        if ('string' === joli.getType(group)) {
+            group = [group];
+        }
 
-      while (i < fieldCount) {
-        rowData[rows.fieldName(i)] = rows.field(i);
-        i++;
-      }
+        this.data.groupBy = group;
+        return this;
+    },
+    hydrate: function(rows, hydratationMode) {
+        var result = [];
 
-      result.push(rowData);
-      rows.next();
-    }
+        if (null === hydratationMode) {
+            hydratationMode = 'object';
+        }
 
-    return result;
-  },
+        if (!rows) {
+            return result;
+        }
 
-  hydrateObject: function(rows, fieldCount) {
-    var result = [];
-    var i;
-    var record;
-    var rowData;
+        var fieldCount;
 
-    // use the model specified by as() first, then from()
-    var model = joli.models.get(this.data.as || this.data.from);
+        if (Titanium.Platform.name !== 'android') {
+            fieldCount = rows.fieldCount();
+        } else {
+            fieldCount = rows.fieldCount;
+        }
 
-    while (rows.isValidRow()) {
-      i = 0;
-      rowData = {};
+        switch (hydratationMode) {
+            case 'array':
+                result = this.hydrateArray(rows, fieldCount);
+                break;
+            case 'object':
+                result = this.hydrateObject(rows, fieldCount);
+                break;
+            default:
+                throw ('Unknown hydratation mode "' + hydratationMode + '". hydratationMode must be "object" or "array"');
+        }
 
-      while (i < fieldCount) {
-        rowData[rows.fieldName(i)] = rows.field(i);
-        i++;
-      }
+        rows.close();
+        return result;
+    },
+    hydrateArray: function(rows, fieldCount) {
+        var result = [];
+        var i;
+        var rowData;
+
+        while (rows.isValidRow()) {
+            i = 0;
+            rowData = {};
+
+            while (i < fieldCount) {
+                rowData[rows.fieldName(i)] = rows.field(i);
+                i++;
+            }
 
-      result.push(model.newRecord().fromArray(rowData));
-      rows.next();
-    }
+            result.push(rowData);
+            rows.next();
+        }
 
-    return result;
-  },
+        return result;
+    },
+    hydrateObject: function(rows, fieldCount) {
+        var result = [];
+        var i;
+        var record;
+        var rowData;
 
-  insertInto: function(table) {
-    this.data.operation = 'insert_into';
-    this.data.from = table;
-    return this;
-  },
+        // use the model specified by as() first, then from()
+        var model = joli.models.get(this.data.as || this.data.from);
 
-  join: function(table, local_id, foreign_id) {
-    this.data.join.push([table, local_id, foreign_id]);
-    return this;
-  },
+        while (rows.isValidRow()) {
+            i = 0;
+            rowData = {};
 
-  limit: function(limit) {
-    this.data.limit = limit;
-    return this;
-  },
+            while (i < fieldCount) {
+                rowData[rows.fieldName(i)] = rows.field(i);
+                i++;
+            }
 
-  order: function(order) {
-    if ('string' == joli.getType(order)) {
-      order = [order];
-    }
+            result.push(model.newRecord().fromArray(rowData));
+            rows.next();
+        }
 
-    this.data.order = order;
-    return this;
-  },
+        return result;
+    },
+    insertInto: function(table) {
+        this.data.operation = 'insert_into';
+        this.data.from = table;
+        return this;
+    },
+    join: function(table, local_id, foreign_id) {
+        this.data.join.push([table, local_id, foreign_id]);
+        return this;
+    },
+    limit: function(limit) {
+        this.data.limit = limit;
+        return this;
+    },
+    order: function(order) {
+        if ('string' === joli.getType(order)) {
+            order = [order];
+        }
 
-  select: function(columns) {
-    this.data.operation = 'select';
+        this.data.order = order;
+        return this;
+    },
+    select: function(columns) {
+        this.data.operation = 'select';
 
-    if (columns) {
-      this.data.select_columns = columns;
-    }
+        if (columns) {
+            this.data.select_columns = columns;
+        }
 
-    return this;
-  },
-
-  set: function(values) {
-    joli.each(values, function(expression, value) {
-      if (-1 === value.indexOf('=')) {
-        this.data.set.push(value + ' = ' + joli.typeValue(expression));
-      } else {
-        // some particular expression containing "="
-        this.data.set.push(value);
-      }
-    }, this);
-    return this;
-  },
-
-  update: function(table) {
-    this.data.operation = 'update';
-    this.data.from = table;
-    return this;
-  },
-
-  values: function(values) {
-    joli.each(values, function(expression, value) {
-      this.data.set.push(value);
-      this.data.values.push(joli.typeValue(expression));
-    }, this);
-    return this;
-  },
-
-  where: function(expression, value) {
-    if (null !== this.data.where) {
-      this.data.where += ' and ';
-    } else {
-      this.data.where = '';
-    }
+        return this;
+    },
+    set: function(values) {
+        joli.each(values, function(expression, value) {
+            if (-1 === value.indexOf('=')) {
+                this.data.set.push(value + ' = ' + joli.typeValue(expression));
+            } else {
+                // some particular expression containing "="
+                this.data.set.push(value);
+            }
+        }, this);
+        return this;
+    },
+    update: function(table) {
+        this.data.operation = 'update';
+        this.data.from = table;
+        return this;
+    },
+    values: function(values) {
+        joli.each(values, function(expression, value) {
+            this.data.set.push(value);
+            this.data.values.push(joli.typeValue(expression));
+        }, this);
+        return this;
+    },
+    where: function(expression, value) {
+        if (null !== this.data.where) {
+            this.data.where += ' and ';
+        } else {
+            this.data.where = '';
+        }
 
-    // handle replacing multiple values
-    if ('array' == joli.getType(value)) {
-      var i = 0;
+        // handle replacing multiple values
+        if ('array' === joli.getType(value)) {
+            var i = 0;
 
-      // replace question marks one at a time from the array
-      while (expression.indexOf('?') != -1 && value[i] !== undefined) {
-        expression = expression.replace(/\?/i, '"' + value[i] + '"');
-        i++;
-      }
+            // replace question marks one at a time from the array
+            while (expression.indexOf('?') !== -1 && value[i] !== undefined) {
+                expression = expression.replace(/\?/i, '"' + value[i] + '"');
+                i++;
+            }
 
-      this.data.where += expression;
-    } else {
-      this.data.where += expression.replace(/\?/gi, '"' + value + '"');
-    }
+            this.data.where += expression;
+        } else {
+            this.data.where += expression.replace(/\?/gi, '"' + value + '"');
+        }
 
-    return this;
-  },
+        return this;
+    },
+    whereIn: function(expression, value) {
+        if (null !== this.data.where) {
+            this.data.where += ' and ';
+        } else {
+            this.data.where = '';
+        }
 
-  whereIn: function(expression, value) {
-    if (null !== this.data.where) {
-      this.data.where += ' and ';
-    } else {
-      this.data.where = '';
-    }
+        if ('array' === joli.getType(value)) {
+            if (0 === value.length) {
+                return this;
+            }
+            value = '(\'' + value.join('\', \'') + '\')';
+        }
 
-    if ('array' == joli.getType(value)) {
-      if (0 === value.length) {
+        this.data.where += expression + ' in ' + value;
         return this;
-      }
-
-      value = '(\'' + value.join('\', \'') + '\')';
     }
-
-    this.data.where += expression + ' in ' + value;
-    return this;
-  }
 };
 
 joli.record = function(table) {
-  this._options = {
-    table: table,
-    columns: table.getColumns()
-  };
-  this._data = {};
-  this._metadata = {};
+    this._options = {
+        table: table,
+        columns: table.getColumns()
+    };
+    this._data = {};
+    this._metadata = {};
 };
 
 joli.record.prototype = {
-  destroy: function() {
-    if (!this.id) {
-      throw("Unsaved record cannot be destroyed");
-    } else {
-      this._options.table.deleteRecords(this.id);
-    }
-  },
-
-  fromArray: function(data) {
-    if (data.id) {
-      this._originalData = { id: data.id };
-      this.isNew = function() {
-        return false;
-      };
-    } else {
-      this._originalData = null;
-      this.isNew = function() {
-        return true;
-      };
-    }
-
-    joli.each(this._options.columns, function(colType, colName) {
-      this[colName] = null;
-      this[colName] = data[colName];
-      this._data[colName] = null;
-      this._data[colName] = data[colName];
+    destroy: function() {
+        if (!this.id) {
+            throw ("Unsaved record cannot be destroyed");
+        } else {
+            this._options.table.deleteRecords(this.id);
+        }
+    },
+    fromArray: function(data) {
+        if (data.id) {
+            this._originalData = {
+                id: data.id
+            };
+            this.isNew = function() {
+                return false;
+            };
+        } else {
+            this._originalData = null;
+            this.isNew = function() {
+                return true;
+            };
+        }
 
-      if (this._originalData && this.isNew()) {
-        this._originalData[colName] = data[colName];
-      }
-    }, this);
+        joli.each(this._options.columns, function(colType, colName) {
+            this[colName] = null;
+            this[colName] = data[colName];
+            this._data[colName] = null;
+            this._data[colName] = data[colName];
 
-    return this;
-  },
+            if (this._originalData && this.isNew()) {
+                this._originalData[colName] = data[colName];
+            }
+        }, this);
+        return this;
+    },
+    get: function(key) {
+        return this[key];
+    },
+    isChanged: function() {
+        if (this.isNew()) {
+            return false;
+        }
 
-  get: function(key) {
-    return this[key];
-  },
+        return !(this.id && joli.toQueryString(this._data) === joli.toQueryString(this._originalData));
+    },
+    save: function() {
+        var data = {
+            data: this._data,
+            metadata: this._metadata
+        };
+
+        if (this.isChanged()) {
+            data.originalData = this._originalData;
+            this._options.table.save(data);
+        } else if (this.isNew()) {
+            this._data.id = this._options.table.save(data);
+        }
 
-  isChanged: function() {
-    if (this.isNew()) {
-      return false;
-    }
+        // overwrite original data so it is no longer "dirty" OR so it is no
+        // longer new
+        this._originalData = {};
+        var newData = {};
 
-    return !(this.id && joli.toQueryString(this._data) === joli.toQueryString(this._originalData));
-  },
+        joli.each(this._options.columns, function(colType, colName) {
+            this._originalData[colName] = this._data[colName];
+            newData[colName] = this._data[colName];
+            this[colName] = this._data[colName];
+        }, this);
 
-  save: function() {
-    var data = {
-      data: this._data,
-      metadata: this._metadata
-    };
+        this._data = newData;
 
-    if (this.isChanged()) {
-      data.originalData = this._originalData;
-      this._options.table.save(data);
-    } else if(this.isNew()) {
-      this._data['id'] = this._options.table.save(data);
+        this.isNew = function() {
+            return false;
+        };
+        return true;
+    },
+    set: function(key, value) {
+        this[key] = value;
+        this._data[key] = value;
+    },
+    toArray: function(data) {
+        var result = [];
+
+        joli.each(this._options.columns, function(colType, colName) {
+            result[colName] = this._data[colName];
+        }, this);
+        return result;
     }
-
-    // overwrite original data so it is no longer "dirty" OR so it is no longer new
-    this._originalData = {};
-    var newData = {};
-
-    joli.each(this._options.columns, function(colType, colName) {
-      this._originalData[colName] = this._data[colName];
-      newData[colName] = this._data[colName];
-      this[colName] = this._data[colName];
-    }, this);
-
-    this._data = newData;
-
-    this.isNew = function() {
-      return false;
-    };
-
-    return true;
-  },
-
-  set: function(key, value) {
-    this[key] = value;
-    this._data[key] = value;
-  },
-
-  toArray: function(data) {
-    var result = [];
-
-    joli.each(this._options.columns, function(colType, colName) {
-      result[colName] = this._data[colName];
-    }, this);
-
-    return result;
-  }
 };
+
+/**
+ * In case joli.js is loaded as a CommonJS module
+ */
+if (typeof exports === 'object' && exports) {
+    exports = function() {
+        var api = {
+            connect: function(database) {
+    
+                if (database) {
+                    joli.connection = new joli.Connection(database);
+                }
+    
+                return joli;
+            }
+        };
+    
+        return api;
+    }();
+}
\ No newline at end of file
