Bläddra i källkod

odoo factory bug fixed in configuration controller

robert 8 år sedan
förälder
incheckning
c6b9c79633
48 ändrade filer med 25190 tillägg och 2 borttagningar
  1. 1884 0
      .vscode/typings/angularjs/angular.d.ts
  2. 80 0
      .vscode/typings/cordova/cordova.d.ts
  3. 391 0
      .vscode/typings/ionic/ionic.d.ts
  4. 3208 0
      .vscode/typings/jquery/jquery.d.ts
  5. 1 0
      jsconfig.json
  6. 78 0
      www/init.sql
  7. 2 2
      www/js/controllers/configuration.controller.js
  8. 50 0
      www/js/factories/preferences.factory.js
  9. 20 0
      www/lib/angular-animate/.bower.json
  10. 68 0
      www/lib/angular-animate/README.md
  11. 4115 0
      www/lib/angular-animate/angular-animate.js
  12. 56 0
      www/lib/angular-animate/angular-animate.min.js
  13. 4 0
      www/lib/angular-animate/angular-animate.min.js.map
  14. 10 0
      www/lib/angular-animate/bower.json
  15. 2 0
      www/lib/angular-animate/index.js
  16. 26 0
      www/lib/angular-animate/package.json
  17. 15 0
      www/lib/squel/.bower.json
  18. 5 0
      www/lib/squel/.gitignore
  19. 4 0
      www/lib/squel/.npmignore
  20. 20 0
      www/lib/squel/.travis.yml
  21. 171 0
      www/lib/squel/CHANGELOG.md
  22. 28 0
      www/lib/squel/CONTRIBUTING.md
  23. 27 0
      www/lib/squel/LICENSE.md
  24. 369 0
      www/lib/squel/README.md
  25. 13 0
      www/lib/squel/RELEASE.md
  26. 3026 0
      www/lib/squel/dist/squel-basic.js
  27. 0 0
      www/lib/squel/dist/squel-basic.min.js
  28. 3761 0
      www/lib/squel/dist/squel.js
  29. 0 0
      www/lib/squel/dist/squel.min.js
  30. 108 0
      www/lib/squel/gulpfile.js
  31. 50 0
      www/lib/squel/package.json
  32. 2180 0
      www/lib/squel/src/core.js
  33. 316 0
      www/lib/squel/src/mssql.js
  34. 68 0
      www/lib/squel/src/mysql.js
  35. 175 0
      www/lib/squel/src/postgres.js
  36. 1147 0
      www/lib/squel/test/baseclasses.test.coffee
  37. 1457 0
      www/lib/squel/test/blocks.test.coffee
  38. 105 0
      www/lib/squel/test/case.test.coffee
  39. 79 0
      www/lib/squel/test/custom.test.coffee
  40. 145 0
      www/lib/squel/test/delete.test.coffee
  41. 330 0
      www/lib/squel/test/expressions.test.coffee
  42. 228 0
      www/lib/squel/test/insert.test.coffee
  43. 161 0
      www/lib/squel/test/mssql.test.coffee
  44. 109 0
      www/lib/squel/test/mysql.test.coffee
  45. 268 0
      www/lib/squel/test/postgres.test.coffee
  46. 481 0
      www/lib/squel/test/select.test.coffee
  47. 65 0
      www/lib/squel/test/testbase.coffee
  48. 284 0
      www/lib/squel/test/update.test.coffee

+ 1884 - 0
.vscode/typings/angularjs/angular.d.ts

@@ -0,0 +1,1884 @@
+// Type definitions for Angular JS 1.4+
+// Project: http://angularjs.org
+// Definitions by: Diego Vilar <http://github.com/diegovilar>
+// Definitions: https://github.com/borisyankov/DefinitelyTyped
+
+
+/// <reference path="../jquery/jquery.d.ts" />
+
+declare var angular: angular.IAngularStatic;
+
+// Support for painless dependency injection
+interface Function {
+    $inject?: string[];
+}
+
+// Collapse angular into ng
+import ng = angular;
+// Support AMD require
+declare module 'angular' {
+    export = angular;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// ng module (angular.js)
+///////////////////////////////////////////////////////////////////////////////
+declare module angular {
+
+    // not directly implemented, but ensures that constructed class implements $get
+    interface IServiceProviderClass {
+        new (...args: any[]): IServiceProvider;
+    }
+
+    interface IServiceProviderFactory {
+        (...args: any[]): IServiceProvider;
+    }
+
+    // All service providers extend this interface
+    interface IServiceProvider {
+        $get: any;
+    }
+
+    interface IAngularBootstrapConfig {
+        strictDi?: boolean;
+        debugInfoEnabled?: boolean;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // AngularStatic
+    // see http://docs.angularjs.org/api
+    ///////////////////////////////////////////////////////////////////////////
+    interface IAngularStatic {
+        bind(context: any, fn: Function, ...args: any[]): Function;
+
+        /**
+         * Use this function to manually start up angular application.
+         *
+         * @param element DOM element which is the root of angular application.
+         * @param modules An array of modules to load into the application.
+         *     Each item in the array should be the name of a predefined module or a (DI annotated)
+         *     function that will be invoked by the injector as a config block.
+         * @param config an object for defining configuration options for the application. The following keys are supported:
+         *     - `strictDi`: disable automatic function annotation for the application. This is meant to assist in finding bugs which break minified code.
+         */
+        bootstrap(element: string|Element|JQuery|Document, modules?: (string|Function|any[])[], config?: IAngularBootstrapConfig): auto.IInjectorService;
+
+        /**
+         * Creates a deep copy of source, which should be an object or an array.
+         *
+         * - If no destination is supplied, a copy of the object or array is created.
+         * - If a destination is provided, all of its elements (for array) or properties (for objects) are deleted and then all elements/properties from the source are copied to it.
+         * - If source is not an object or array (inc. null and undefined), source is returned.
+         * - If source is identical to 'destination' an exception will be thrown.
+         *
+         * @param source The source that will be used to make a copy. Can be any type, including primitives, null, and undefined.
+         * @param destination Destination into which the source is copied. If provided, must be of the same type as source.
+         */
+        copy<T>(source: T, destination?: T): T;
+
+        /**
+         * Wraps a raw DOM element or HTML string as a jQuery element.
+         *
+         * If jQuery is available, angular.element is an alias for the jQuery function. If jQuery is not available, angular.element delegates to Angular's built-in subset of jQuery, called "jQuery lite" or "jqLite."
+         */
+        element: IAugmentedJQueryStatic;
+        equals(value1: any, value2: any): boolean;
+        extend(destination: any, ...sources: any[]): any;
+
+        /**
+         * Invokes the iterator function once for each item in obj collection, which can be either an object or an array. The iterator function is invoked with iterator(value, key), where value is the value of an object property or an array element and key is the object property key or array element index. Specifying a context for the function is optional.
+         *
+         * It is worth noting that .forEach does not iterate over inherited properties because it filters using the hasOwnProperty method.
+         *
+         * @param obj Object to iterate over.
+         * @param iterator Iterator function.
+         * @param context Object to become context (this) for the iterator function.
+         */
+        forEach<T>(obj: T[], iterator: (value: T, key: number) => any, context?: any): any;
+        /**
+         * Invokes the iterator function once for each item in obj collection, which can be either an object or an array. The iterator function is invoked with iterator(value, key), where value is the value of an object property or an array element and key is the object property key or array element index. Specifying a context for the function is optional.
+         *
+         * It is worth noting that .forEach does not iterate over inherited properties because it filters using the hasOwnProperty method.
+         *
+         * @param obj Object to iterate over.
+         * @param iterator Iterator function.
+         * @param context Object to become context (this) for the iterator function.
+         */
+        forEach<T>(obj: { [index: string]: T; }, iterator: (value: T, key: string) => any, context?: any): any;
+        /**
+         * Invokes the iterator function once for each item in obj collection, which can be either an object or an array. The iterator function is invoked with iterator(value, key), where value is the value of an object property or an array element and key is the object property key or array element index. Specifying a context for the function is optional.
+         *
+         * It is worth noting that .forEach does not iterate over inherited properties because it filters using the hasOwnProperty method.
+         *
+         * @param obj Object to iterate over.
+         * @param iterator Iterator function.
+         * @param context Object to become context (this) for the iterator function.
+         */
+        forEach(obj: any, iterator: (value: any, key: any) => any, context?: any): any;
+
+        fromJson(json: string): any;
+        identity<T>(arg?: T): T;
+        injector(modules?: any[], strictDi?: boolean): auto.IInjectorService;
+        isArray(value: any): boolean;
+        isDate(value: any): boolean;
+        isDefined(value: any): boolean;
+        isElement(value: any): boolean;
+        isFunction(value: any): boolean;
+        isNumber(value: any): boolean;
+        isObject(value: any): boolean;
+        isString(value: any): boolean;
+        isUndefined(value: any): boolean;
+        lowercase(str: string): string;
+
+        /**
+         * Deeply extends the destination object dst by copying own enumerable properties from the src object(s) to dst. You can specify multiple src objects. If you want to preserve original objects, you can do so by passing an empty object as the target: var object = angular.merge({}, object1, object2).
+         *
+         * Unlike extend(), merge() recursively descends into object properties of source objects, performing a deep copy.
+         *
+         * @param dst Destination object.
+         * @param src Source object(s).
+         */
+        merge(dst: any, ...src: any[]): any;
+
+        /**
+         * The angular.module is a global place for creating, registering and retrieving Angular modules. All modules (angular core or 3rd party) that should be available to an application must be registered using this mechanism.
+         *
+         * When passed two or more arguments, a new module is created. If passed only one argument, an existing module (the name passed as the first argument to module) is retrieved.
+         *
+         * @param name The name of the module to create or retrieve.
+         * @param requires The names of modules this module depends on. If specified then new module is being created. If unspecified then the module is being retrieved for further configuration.
+         * @param configFn Optional configuration function for the module.
+         */
+        module(
+            name: string,
+            requires?: string[],
+            configFn?: Function): IModule;
+
+        noop(...args: any[]): void;
+        reloadWithDebugInfo(): void;
+        toJson(obj: any, pretty?: boolean): string;
+        uppercase(str: string): string;
+        version: {
+            full: string;
+            major: number;
+            minor: number;
+            dot: number;
+            codeName: string;
+        };
+
+        /**
+         * If window.name contains prefix NG_DEFER_BOOTSTRAP! when angular.bootstrap is called, the bootstrap process will be paused until angular.resumeBootstrap() is called.
+         * @param extraModules An optional array of modules that should be added to the original list of modules that the app was about to be bootstrapped with.
+         */
+        resumeBootstrap?(extraModules?: string[]): ng.auto.IInjectorService;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Module
+    // see http://docs.angularjs.org/api/angular.Module
+    ///////////////////////////////////////////////////////////////////////////
+    interface IModule {
+        animation(name: string, animationFactory: Function): IModule;
+        animation(name: string, inlineAnnotatedFunction: any[]): IModule;
+        animation(object: Object): IModule;
+        /**
+         * Use this method to register a component.
+         *
+         * @param name The name of the component.
+         * @param options A definition object passed into the component.
+         */
+        component(name: string, options: IComponentOptions): IModule;
+        /**
+         * Use this method to register work which needs to be performed on module loading.
+         *
+         * @param configFn Execute this function on module load. Useful for service configuration.
+         */
+        config(configFn: Function): IModule;
+        /**
+         * Use this method to register work which needs to be performed on module loading.
+         *
+         * @param inlineAnnotatedFunction Execute this function on module load. Useful for service configuration.
+         */
+        config(inlineAnnotatedFunction: any[]): IModule;
+        config(object: Object): IModule;
+        /**
+         * Register a constant service, such as a string, a number, an array, an object or a function, with the $injector. Unlike value it can be injected into a module configuration function (see config) and it cannot be overridden by an Angular decorator.
+         *
+         * @param name The name of the constant.
+         * @param value The constant value.
+         */
+        constant(name: string, value: any): IModule;
+        constant(object: Object): IModule;
+        /**
+         * The $controller service is used by Angular to create new controllers.
+         *
+         * This provider allows controller registration via the register method.
+         *
+         * @param name Controller name, or an object map of controllers where the keys are the names and the values are the constructors.
+         * @param controllerConstructor Controller constructor fn (optionally decorated with DI annotations in the array notation).
+         */
+        controller(name: string, controllerConstructor: Function): IModule;
+        /**
+         * The $controller service is used by Angular to create new controllers.
+         *
+         * This provider allows controller registration via the register method.
+         *
+         * @param name Controller name, or an object map of controllers where the keys are the names and the values are the constructors.
+         * @param controllerConstructor Controller constructor fn (optionally decorated with DI annotations in the array notation).
+         */
+        controller(name: string, inlineAnnotatedConstructor: any[]): IModule;
+        controller(object: Object): IModule;
+        /**
+         * Register a new directive with the compiler.
+         *
+         * @param name Name of the directive in camel-case (i.e. ngBind which will match as ng-bind)
+         * @param directiveFactory An injectable directive factory function.
+         */
+        directive(name: string, directiveFactory: IDirectiveFactory): IModule;
+        /**
+         * Register a new directive with the compiler.
+         *
+         * @param name Name of the directive in camel-case (i.e. ngBind which will match as ng-bind)
+         * @param directiveFactory An injectable directive factory function.
+         */
+        directive(name: string, inlineAnnotatedFunction: any[]): IModule;
+        directive(object: Object): IModule;
+        /**
+         * Register a service factory, which will be called to return the service instance. This is short for registering a service where its provider consists of only a $get property, which is the given service factory function. You should use $provide.factory(getFn) if you do not need to configure your service in a provider.
+         *
+         * @param name The name of the instance.
+         * @param $getFn The $getFn for the instance creation. Internally this is a short hand for $provide.provider(name, {$get: $getFn}).
+         */
+        factory(name: string, $getFn: Function): IModule;
+        /**
+         * Register a service factory, which will be called to return the service instance. This is short for registering a service where its provider consists of only a $get property, which is the given service factory function. You should use $provide.factory(getFn) if you do not need to configure your service in a provider.
+         *
+         * @param name The name of the instance.
+         * @param inlineAnnotatedFunction The $getFn for the instance creation. Internally this is a short hand for $provide.provider(name, {$get: $getFn}).
+         */
+        factory(name: string, inlineAnnotatedFunction: any[]): IModule;
+        factory(object: Object): IModule;
+        filter(name: string, filterFactoryFunction: Function): IModule;
+        filter(name: string, inlineAnnotatedFunction: any[]): IModule;
+        filter(object: Object): IModule;
+        provider(name: string, serviceProviderFactory: IServiceProviderFactory): IModule;
+        provider(name: string, serviceProviderConstructor: IServiceProviderClass): IModule;
+        provider(name: string, inlineAnnotatedConstructor: any[]): IModule;
+        provider(name: string, providerObject: IServiceProvider): IModule;
+        provider(object: Object): IModule;
+        /**
+         * Run blocks are the closest thing in Angular to the main method. A run block is the code which needs to run to kickstart the application. It is executed after all of the service have been configured and the injector has been created. Run blocks typically contain code which is hard to unit-test, and for this reason should be declared in isolated modules, so that they can be ignored in the unit-tests.
+         */
+        run(initializationFunction: Function): IModule;
+        /**
+         * Run blocks are the closest thing in Angular to the main method. A run block is the code which needs to run to kickstart the application. It is executed after all of the service have been configured and the injector has been created. Run blocks typically contain code which is hard to unit-test, and for this reason should be declared in isolated modules, so that they can be ignored in the unit-tests.
+         */
+        run(inlineAnnotatedFunction: any[]): IModule;
+        service(name: string, serviceConstructor: Function): IModule;
+        service(name: string, inlineAnnotatedConstructor: any[]): IModule;
+        service(object: Object): IModule;
+        /**
+         * Register a value service with the $injector, such as a string, a number, an array, an object or a function. This is short for registering a service where its provider's $get property is a factory function that takes no arguments and returns the value service.
+           Value services are similar to constant services, except that they cannot be injected into a module configuration function (see config) but they can be overridden by an Angular decorator.
+         *
+         * @param name The name of the instance.
+         * @param value The value.
+         */
+        value(name: string, value: any): IModule;
+        value(object: Object): IModule;
+
+        /**
+         * Register a service decorator with the $injector. A service decorator intercepts the creation of a service, allowing it to override or modify the behaviour of the service. The object returned by the decorator may be the original service, or a new service object which replaces or wraps and delegates to the original service.
+         * @param name The name of the service to decorate
+         * @param decorator This function will be invoked when the service needs to be instantiated and should return the decorated service instance. The function is called using the injector.invoke method and is therefore fully injectable. Local injection arguments: $delegate - The original service instance, which can be monkey patched, configured, decorated or delegated to.
+         */
+        decorator(name:string, decoratorConstructor: Function): IModule;
+        decorator(name:string, inlineAnnotatedConstructor: any[]): IModule;
+
+        // Properties
+        name: string;
+        requires: string[];
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Attributes
+    // see http://docs.angularjs.org/api/ng.$compile.directive.Attributes
+    ///////////////////////////////////////////////////////////////////////////
+    interface IAttributes {
+        /**
+         * this is necessary to be able to access the scoped attributes. it's not very elegant
+         * because you have to use attrs['foo'] instead of attrs.foo but I don't know of a better way
+         * this should really be limited to return string but it creates this problem: http://stackoverflow.com/q/17201854/165656
+         */
+        [name: string]: any;
+
+        /**
+         * Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with x- or data-) to its normalized, camelCase form.
+         *
+         * Also there is special case for Moz prefix starting with upper case letter.
+         *
+         * For further information check out the guide on @see https://docs.angularjs.org/guide/directive#matching-directives
+         */
+        $normalize(name: string): string;
+
+        /**
+         * Adds the CSS class value specified by the classVal parameter to the
+         * element. If animations are enabled then an animation will be triggered
+         * for the class addition.
+         */
+        $addClass(classVal: string): void;
+
+        /**
+         * Removes the CSS class value specified by the classVal parameter from the
+         * element. If animations are enabled then an animation will be triggered for
+         * the class removal.
+         */
+        $removeClass(classVal: string): void;
+
+        /**
+         * Set DOM element attribute value.
+         */
+        $set(key: string, value: any): void;
+
+        /**
+         * Observes an interpolated attribute.
+         * The observer function will be invoked once during the next $digest
+         * following compilation. The observer is then invoked whenever the
+         * interpolated value changes.
+         */
+        $observe<T>(name: string, fn: (value?: T) => any): Function;
+
+        /**
+         * A map of DOM element attribute names to the normalized name. This is needed
+         * to do reverse lookup from normalized name back to actual name.
+         */
+        $attr: Object;
+    }
+
+    /**
+     * form.FormController - type in module ng
+     * see https://docs.angularjs.org/api/ng/type/form.FormController
+     */
+    interface IFormController {
+
+        /**
+         * Indexer which should return ng.INgModelController for most properties but cannot because of "All named properties must be assignable to string indexer type" constraint - see https://github.com/Microsoft/TypeScript/issues/272
+         */
+        [name: string]: any;
+
+        $pristine: boolean;
+        $dirty: boolean;
+        $valid: boolean;
+        $invalid: boolean;
+        $submitted: boolean;
+        $error: any;
+        $addControl(control: INgModelController): void;
+        $removeControl(control: INgModelController): void;
+        $setValidity(validationErrorKey: string, isValid: boolean, control: INgModelController): void;
+        $setDirty(): void;
+        $setPristine(): void;
+        $commitViewValue(): void;
+        $rollbackViewValue(): void;
+        $setSubmitted(): void;
+        $setUntouched(): void;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // NgModelController
+    // see http://docs.angularjs.org/api/ng.directive:ngModel.NgModelController
+    ///////////////////////////////////////////////////////////////////////////
+    interface INgModelController {
+        $render(): void;
+        $setValidity(validationErrorKey: string, isValid: boolean): void;
+        // Documentation states viewValue and modelValue to be a string but other
+        // types do work and it's common to use them.
+        $setViewValue(value: any, trigger?: string): void;
+        $setPristine(): void;
+        $setDirty(): void;
+        $validate(): void;
+        $setTouched(): void;
+        $setUntouched(): void;
+        $rollbackViewValue(): void;
+        $commitViewValue(): void;
+        $isEmpty(value: any): boolean;
+
+        $viewValue: any;
+
+        $modelValue: any;
+
+        $parsers: IModelParser[];
+        $formatters: IModelFormatter[];
+        $viewChangeListeners: IModelViewChangeListener[];
+        $error: any;
+        $name: string;
+
+        $touched: boolean;
+        $untouched: boolean;
+
+        $validators: IModelValidators;
+        $asyncValidators: IAsyncModelValidators;
+
+        $pending: any;
+        $pristine: boolean;
+        $dirty: boolean;
+        $valid: boolean;
+        $invalid: boolean;
+    }
+
+    interface IModelValidators {
+        /**
+         * viewValue is any because it can be an object that is called in the view like $viewValue.name:$viewValue.subName
+         */
+        [index: string]: (modelValue: any, viewValue: any) => boolean;
+    }
+
+    interface IAsyncModelValidators {
+        [index: string]: (modelValue: any, viewValue: any) => IPromise<any>;
+    }
+
+    interface IModelParser {
+        (value: any): any;
+    }
+
+    interface IModelFormatter {
+        (value: any): any;
+    }
+
+    interface IModelViewChangeListener {
+        (): void;
+    }
+
+    /**
+     * $rootScope - $rootScopeProvider - service in module ng
+     * see https://docs.angularjs.org/api/ng/type/$rootScope.Scope and https://docs.angularjs.org/api/ng/service/$rootScope
+     */
+    interface IRootScopeService {
+        [index: string]: any;
+
+        $apply(): any;
+        $apply(exp: string): any;
+        $apply(exp: (scope: IScope) => any): any;
+
+        $applyAsync(): any;
+        $applyAsync(exp: string): any;
+        $applyAsync(exp: (scope: IScope) => any): any;
+
+        /**
+         * Dispatches an event name downwards to all child scopes (and their children) notifying the registered $rootScope.Scope listeners.
+         *
+         * The event life cycle starts at the scope on which $broadcast was called. All listeners listening for name event on this scope get notified. Afterwards, the event propagates to all direct and indirect scopes of the current scope and calls all registered listeners along the way. The event cannot be canceled.
+         *
+         * Any exception emitted from the listeners will be passed onto the $exceptionHandler service.
+         *
+         * @param name Event name to broadcast.
+         * @param args Optional one or more arguments which will be passed onto the event listeners.
+         */
+        $broadcast(name: string, ...args: any[]): IAngularEvent;
+        $destroy(): void;
+        $digest(): void;
+        /**
+         * Dispatches an event name upwards through the scope hierarchy notifying the registered $rootScope.Scope listeners.
+         *
+         * The event life cycle starts at the scope on which $emit was called. All listeners listening for name event on this scope get notified. Afterwards, the event traverses upwards toward the root scope and calls all registered listeners along the way. The event will stop propagating if one of the listeners cancels it.
+         *
+         * Any exception emitted from the listeners will be passed onto the $exceptionHandler service.
+         *
+         * @param name Event name to emit.
+         * @param args Optional one or more arguments which will be passed onto the event listeners.
+         */
+        $emit(name: string, ...args: any[]): IAngularEvent;
+
+        $eval(): any;
+        $eval(expression: string, locals?: Object): any;
+        $eval(expression: (scope: IScope) => any, locals?: Object): any;
+
+        $evalAsync(): void;
+        $evalAsync(expression: string): void;
+        $evalAsync(expression: (scope: IScope) => any): void;
+
+        // Defaults to false by the implementation checking strategy
+        $new(isolate?: boolean, parent?: IScope): IScope;
+
+        /**
+         * Listens on events of a given type. See $emit for discussion of event life cycle.
+         *
+         * The event listener function format is: function(event, args...).
+         *
+         * @param name Event name to listen on.
+         * @param listener Function to call when the event is emitted.
+         */
+        $on(name: string, listener: (event: IAngularEvent, ...args: any[]) => any): Function;
+
+        $watch(watchExpression: string, listener?: string, objectEquality?: boolean): Function;
+        $watch<T>(watchExpression: string, listener?: (newValue: T, oldValue: T, scope: IScope) => any, objectEquality?: boolean): Function;
+        $watch(watchExpression: (scope: IScope) => any, listener?: string, objectEquality?: boolean): Function;
+        $watch<T>(watchExpression: (scope: IScope) => T, listener?: (newValue: T, oldValue: T, scope: IScope) => any, objectEquality?: boolean): Function;
+
+        $watchCollection<T>(watchExpression: string, listener: (newValue: T, oldValue: T, scope: IScope) => any): Function;
+        $watchCollection<T>(watchExpression: (scope: IScope) => T, listener: (newValue: T, oldValue: T, scope: IScope) => any): Function;
+
+        $watchGroup(watchExpressions: any[], listener: (newValue: any, oldValue: any, scope: IScope) => any): Function;
+        $watchGroup(watchExpressions: { (scope: IScope): any }[], listener: (newValue: any, oldValue: any, scope: IScope) => any): Function;
+
+        $parent: IScope;
+        $root: IRootScopeService;
+        $id: number;
+
+        // Hidden members
+        $$isolateBindings: any;
+        $$phase: any;
+    }
+
+    interface IScope extends IRootScopeService { }
+
+    /**
+     * $scope for ngRepeat directive.
+     * see https://docs.angularjs.org/api/ng/directive/ngRepeat
+     */
+    interface IRepeatScope extends IScope {
+
+        /**
+         * iterator offset of the repeated element (0..length-1).
+         */
+        $index: number;
+
+        /**
+         * true if the repeated element is first in the iterator.
+         */
+        $first: boolean;
+
+        /**
+         * true if the repeated element is between the first and last in the iterator.
+         */
+        $middle: boolean;
+
+        /**
+         * true if the repeated element is last in the iterator.
+         */
+        $last: boolean;
+
+        /**
+         * true if the iterator position $index is even (otherwise false).
+         */
+        $even: boolean;
+
+        /**
+         * true if the iterator position $index is odd (otherwise false).
+         */
+        $odd: boolean;
+
+    }
+
+    interface IAngularEvent {
+        /**
+         * the scope on which the event was $emit-ed or $broadcast-ed.
+         */
+        targetScope: IScope;
+        /**
+         * the scope that is currently handling the event. Once the event propagates through the scope hierarchy, this property is set to null.
+         */
+        currentScope: IScope;
+        /**
+         * name of the event.
+         */
+        name: string;
+        /**
+         * calling stopPropagation function will cancel further event propagation (available only for events that were $emit-ed).
+         */
+        stopPropagation?: Function;
+        /**
+         * calling preventDefault sets defaultPrevented flag to true.
+         */
+        preventDefault: Function;
+        /**
+         * true if preventDefault was called.
+         */
+        defaultPrevented: boolean;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // WindowService
+    // see http://docs.angularjs.org/api/ng.$window
+    ///////////////////////////////////////////////////////////////////////////
+    interface IWindowService extends Window {
+        [key: string]: any;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // BrowserService
+    // TODO undocumented, so we need to get it from the source code
+    ///////////////////////////////////////////////////////////////////////////
+    interface IBrowserService {
+        defer: angular.ITimeoutService;
+        [key: string]: any;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // TimeoutService
+    // see http://docs.angularjs.org/api/ng.$timeout
+    ///////////////////////////////////////////////////////////////////////////
+    interface ITimeoutService {
+        (delay?: number, invokeApply?: boolean): IPromise<void>;
+        <T>(fn: (...args: any[]) => T, delay?: number, invokeApply?: boolean, ...args: any[]): IPromise<T>;
+        cancel(promise?: IPromise<any>): boolean;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // IntervalService
+    // see http://docs.angularjs.org/api/ng.$interval
+    ///////////////////////////////////////////////////////////////////////////
+    interface IIntervalService {
+        (func: Function, delay: number, count?: number, invokeApply?: boolean, ...args: any[]): IPromise<any>;
+        cancel(promise: IPromise<any>): boolean;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // AnimateProvider
+    // see http://docs.angularjs.org/api/ng/provider/$animateProvider
+    ///////////////////////////////////////////////////////////////////////////
+    interface IAnimateProvider {
+        /**
+         * Registers a new injectable animation factory function.
+         *
+         * @param name The name of the animation.
+         * @param factory The factory function that will be executed to return the animation object.
+         */
+        register(name: string, factory: () => IAnimateCallbackObject): void;
+
+        /**
+         * Gets and/or sets the CSS class expression that is checked when performing an animation.
+         *
+         * @param expression The className expression which will be checked against all animations.
+         * @returns The current CSS className expression value. If null then there is no expression value.
+         */
+        classNameFilter(expression?: RegExp): RegExp;
+    }
+
+    /**
+     * The animation object which contains callback functions for each event that is expected to be animated.
+     */
+    interface IAnimateCallbackObject {
+        eventFn(element: Node, doneFn: () => void): Function;
+    }
+
+    /**
+     * $filter - $filterProvider - service in module ng
+     *
+     * Filters are used for formatting data displayed to the user.
+     *
+     * see https://docs.angularjs.org/api/ng/service/$filter
+     */
+    interface IFilterService {
+        (name: 'filter'): IFilterFilter;
+        (name: 'currency'): IFilterCurrency;
+        (name: 'number'): IFilterNumber;
+        (name: 'date'): IFilterDate;
+        (name: 'json'): IFilterJson;
+        (name: 'lowercase'): IFilterLowercase;
+        (name: 'uppercase'): IFilterUppercase;
+        (name: 'limitTo'): IFilterLimitTo;
+        (name: 'orderBy'): IFilterOrderBy;
+        /**
+         * Usage:
+         * $filter(name);
+         *
+         * @param name Name of the filter function to retrieve
+         */
+        <T>(name: string): T;
+    }
+
+    interface IFilterFilter {
+        <T>(array: T[], expression: string | IFilterFilterPatternObject | IFilterFilterPredicateFunc<T>, comparator?: IFilterFilterComparatorFunc<T>|boolean): T[];
+    }
+
+    interface IFilterFilterPatternObject {
+        [name: string]: any;
+    }
+
+    interface IFilterFilterPredicateFunc<T> {
+        (value: T, index: number, array: T[]): boolean;
+    }
+
+    interface IFilterFilterComparatorFunc<T> {
+        (actual: T, expected: T): boolean;
+    }
+
+    interface IFilterCurrency {
+        /**
+         * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default symbol for current locale is used.
+         * @param amount Input to filter.
+         * @param symbol Currency symbol or identifier to be displayed.
+         * @param fractionSize Number of decimal places to round the amount to, defaults to default max fraction size for current locale
+         * @return Formatted number
+         */
+        (amount: number, symbol?: string, fractionSize?: number): string;
+    }
+
+    interface IFilterNumber {
+        /**
+         * Formats a number as text.
+         * @param number Number to format.
+         * @param fractionSize Number of decimal places to round the number to. If this is not provided then the fraction size is computed from the current locale's number formatting pattern. In the case of the default locale, it will be 3.
+         * @return Number rounded to decimalPlaces and places a “,” after each third digit.
+         */
+        (value: number|string, fractionSize?: number|string): string;
+    }
+
+    interface IFilterDate {
+        /**
+         * Formats date to a string based on the requested format.
+         *
+         * @param date Date to format either as Date object, milliseconds (string or number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.sssZ and its shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is specified in the string input, the time is considered to be in the local timezone.
+         * @param format Formatting rules (see Description). If not specified, mediumDate is used.
+         * @param timezone Timezone to be used for formatting. It understands UTC/GMT and the continental US time zone abbreviations, but for general use, use a time zone offset, for example, '+0430' (4 hours, 30 minutes east of the Greenwich meridian) If not specified, the timezone of the browser will be used.
+         * @return Formatted string or the input if input is not recognized as date/millis.
+         */
+        (date: Date | number | string, format?: string, timezone?: string): string;
+    }
+
+    interface IFilterJson {
+        /**
+         * Allows you to convert a JavaScript object into JSON string.
+         * @param object Any JavaScript object (including arrays and primitive types) to filter.
+         * @param spacing The number of spaces to use per indentation, defaults to 2.
+         * @return JSON string.
+         */
+        (object: any, spacing?: number): string;
+    }
+
+    interface IFilterLowercase {
+        /**
+         * Converts string to lowercase.
+         */
+        (value: string): string;
+    }
+
+    interface IFilterUppercase {
+        /**
+         * Converts string to uppercase.
+         */
+        (value: string): string;
+    }
+
+    interface IFilterLimitTo {
+        /**
+         * Creates a new array containing only a specified number of elements. The elements are taken from either the beginning or the end of the source array, string or number, as specified by the value and sign (positive or negative) of limit.
+         * @param input Source array to be limited.
+         * @param limit The length of the returned array. If the limit number is positive, limit number of items from the beginning of the source array/string are copied. If the number is negative, limit number of items from the end of the source array are copied. The limit will be trimmed if it exceeds array.length. If limit is undefined, the input will be returned unchanged.
+         * @param begin Index at which to begin limitation. As a negative index, begin indicates an offset from the end of input. Defaults to 0.
+         * @return A new sub-array of length limit or less if input array had less than limit elements.
+         */
+        <T>(input: T[], limit: string|number, begin?: string|number): T[];
+        /**
+         * Creates a new string containing only a specified number of elements. The elements are taken from either the beginning or the end of the source string or number, as specified by the value and sign (positive or negative) of limit. If a number is used as input, it is converted to a string.
+         * @param input Source string or number to be limited.
+         * @param limit The length of the returned string. If the limit number is positive, limit number of items from the beginning of the source string are copied. If the number is negative, limit number of items from the end of the source string are copied. The limit will be trimmed if it exceeds input.length. If limit is undefined, the input will be returned unchanged.
+         * @param begin Index at which to begin limitation. As a negative index, begin indicates an offset from the end of input. Defaults to 0.
+         * @return A new substring of length limit or less if input had less than limit elements.
+         */
+        (input: string|number, limit: string|number, begin?: string|number): string;
+    }
+
+    interface IFilterOrderBy {
+        /**
+         * Orders a specified array by the expression predicate. It is ordered alphabetically for strings and numerically for numbers. Note: if you notice numbers are not being sorted as expected, make sure they are actually being saved as numbers and not strings.
+         * @param array The array to sort.
+         * @param expression A predicate to be used by the comparator to determine the order of elements.
+         * @param reverse Reverse the order of the array.
+         * @return Reverse the order of the array.
+         */
+        <T>(array: T[], expression: string|((value: T) => any)|(((value: T) => any)|string)[], reverse?: boolean): T[];
+    }
+
+    /**
+     * $filterProvider - $filter - provider in module ng
+     *
+     * Filters are just functions which transform input to an output. However filters need to be Dependency Injected. To achieve this a filter definition consists of a factory function which is annotated with dependencies and is responsible for creating a filter function.
+     *
+     * see https://docs.angularjs.org/api/ng/provider/$filterProvider
+     */
+    interface IFilterProvider extends IServiceProvider {
+        /**
+         * register(name);
+         *
+         * @param name Name of the filter function, or an object map of filters where the keys are the filter names and the values are the filter factories. Note: Filter names must be valid angular Expressions identifiers, such as uppercase or orderBy. Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace your filters, then you can use capitalization (myappSubsectionFilterx) or underscores (myapp_subsection_filterx).
+         */
+        register(name: string | {}): IServiceProvider;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // LocaleService
+    // see http://docs.angularjs.org/api/ng.$locale
+    ///////////////////////////////////////////////////////////////////////////
+    interface ILocaleService {
+        id: string;
+
+        // These are not documented
+        // Check angular's i18n files for exemples
+        NUMBER_FORMATS: ILocaleNumberFormatDescriptor;
+        DATETIME_FORMATS: ILocaleDateTimeFormatDescriptor;
+        pluralCat: (num: any) => string;
+    }
+
+    interface ILocaleNumberFormatDescriptor {
+        DECIMAL_SEP: string;
+        GROUP_SEP: string;
+        PATTERNS: ILocaleNumberPatternDescriptor[];
+        CURRENCY_SYM: string;
+    }
+
+    interface ILocaleNumberPatternDescriptor {
+        minInt: number;
+        minFrac: number;
+        maxFrac: number;
+        posPre: string;
+        posSuf: string;
+        negPre: string;
+        negSuf: string;
+        gSize: number;
+        lgSize: number;
+    }
+
+    interface ILocaleDateTimeFormatDescriptor {
+        MONTH: string[];
+        SHORTMONTH: string[];
+        DAY: string[];
+        SHORTDAY: string[];
+        AMPMS: string[];
+        medium: string;
+        short: string;
+        fullDate: string;
+        longDate: string;
+        mediumDate: string;
+        shortDate: string;
+        mediumTime: string;
+        shortTime: string;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // LogService
+    // see http://docs.angularjs.org/api/ng.$log
+    // see http://docs.angularjs.org/api/ng.$logProvider
+    ///////////////////////////////////////////////////////////////////////////
+    interface ILogService {
+        debug: ILogCall;
+        error: ILogCall;
+        info: ILogCall;
+        log: ILogCall;
+        warn: ILogCall;
+    }
+
+    interface ILogProvider extends IServiceProvider {
+        debugEnabled(): boolean;
+        debugEnabled(enabled: boolean): ILogProvider;
+    }
+
+    // We define this as separate interface so we can reopen it later for
+    // the ngMock module.
+    interface ILogCall {
+        (...args: any[]): void;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // ParseService
+    // see http://docs.angularjs.org/api/ng.$parse
+    // see http://docs.angularjs.org/api/ng.$parseProvider
+    ///////////////////////////////////////////////////////////////////////////
+    interface IParseService {
+        (expression: string): ICompiledExpression;
+    }
+
+    interface IParseProvider {
+        logPromiseWarnings(): boolean;
+        logPromiseWarnings(value: boolean): IParseProvider;
+
+        unwrapPromises(): boolean;
+        unwrapPromises(value: boolean): IParseProvider;
+    }
+
+    interface ICompiledExpression {
+        (context: any, locals?: any): any;
+
+        literal: boolean;
+        constant: boolean;
+
+        // If value is not provided, undefined is gonna be used since the implementation
+        // does not check the parameter. Let's force a value for consistency. If consumer
+        // whants to undefine it, pass the undefined value explicitly.
+        assign(context: any, value: any): any;
+    }
+
+    /**
+     * $location - $locationProvider - service in module ng
+     * see https://docs.angularjs.org/api/ng/service/$location
+     */
+    interface ILocationService {
+        absUrl(): string;
+        hash(): string;
+        hash(newHash: string): ILocationService;
+        host(): string;
+
+        /**
+         * Return path of current url
+         */
+        path(): string;
+
+        /**
+         * Change path when called with parameter and return $location.
+         * Note: Path should always begin with forward slash (/), this method will add the forward slash if it is missing.
+         *
+         * @param path New path
+         */
+        path(path: string): ILocationService;
+
+        port(): number;
+        protocol(): string;
+        replace(): ILocationService;
+
+        /**
+         * Return search part (as object) of current url
+         */
+        search(): any;
+
+        /**
+         * Change search part when called with parameter and return $location.
+         *
+         * @param search When called with a single argument the method acts as a setter, setting the search component of $location to the specified value.
+         *
+         * If the argument is a hash object containing an array of values, these values will be encoded as duplicate search parameters in the url.
+         */
+        search(search: any): ILocationService;
+
+        /**
+         * Change search part when called with parameter and return $location.
+         *
+         * @param search New search params
+         * @param paramValue If search is a string or a Number, then paramValue will override only a single search property. If paramValue is null, the property specified via the first argument will be deleted. If paramValue is an array, it will override the property of the search component of $location specified via the first argument. If paramValue is true, the property specified via the first argument will be added with no value nor trailing equal sign.
+         */
+        search(search: string, paramValue: string|number|string[]|boolean): ILocationService;
+
+        state(): any;
+        state(state: any): ILocationService;
+        url(): string;
+        url(url: string): ILocationService;
+    }
+
+    interface ILocationProvider extends IServiceProvider {
+        hashPrefix(): string;
+        hashPrefix(prefix: string): ILocationProvider;
+        html5Mode(): boolean;
+
+        // Documentation states that parameter is string, but
+        // implementation tests it as boolean, which makes more sense
+        // since this is a toggler
+        html5Mode(active: boolean): ILocationProvider;
+        html5Mode(mode: { enabled?: boolean; requireBase?: boolean; rewriteLinks?: boolean; }): ILocationProvider;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // DocumentService
+    // see http://docs.angularjs.org/api/ng.$document
+    ///////////////////////////////////////////////////////////////////////////
+    interface IDocumentService extends IAugmentedJQuery {}
+
+    ///////////////////////////////////////////////////////////////////////////
+    // ExceptionHandlerService
+    // see http://docs.angularjs.org/api/ng.$exceptionHandler
+    ///////////////////////////////////////////////////////////////////////////
+    interface IExceptionHandlerService {
+        (exception: Error, cause?: string): void;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // RootElementService
+    // see http://docs.angularjs.org/api/ng.$rootElement
+    ///////////////////////////////////////////////////////////////////////////
+    interface IRootElementService extends JQuery {}
+
+    interface IQResolveReject<T> {
+        (): void;
+        (value: T): void;
+    }
+    /**
+     * $q - service in module ng
+     * A promise/deferred implementation inspired by Kris Kowal's Q.
+     * See http://docs.angularjs.org/api/ng/service/$q
+     */
+    interface IQService {
+        new <T>(resolver: (resolve: IQResolveReject<T>) => any): IPromise<T>;
+        new <T>(resolver: (resolve: IQResolveReject<T>, reject: IQResolveReject<any>) => any): IPromise<T>;
+        <T>(resolver: (resolve: IQResolveReject<T>) => any): IPromise<T>;
+        <T>(resolver: (resolve: IQResolveReject<T>, reject: IQResolveReject<any>) => any): IPromise<T>;
+
+        /**
+         * Combines multiple promises into a single promise that is resolved when all of the input promises are resolved.
+         *
+         * Returns a single promise that will be resolved with an array of values, each value corresponding to the promise at the same index in the promises array. If any of the promises is resolved with a rejection, this resulting promise will be rejected with the same rejection value.
+         *
+         * @param promises An array of promises.
+         */
+        all<T>(promises: IPromise<any>[]): IPromise<T[]>;
+        /**
+         * Combines multiple promises into a single promise that is resolved when all of the input promises are resolved.
+         *
+         * Returns a single promise that will be resolved with a hash of values, each value corresponding to the promise at the same key in the promises hash. If any of the promises is resolved with a rejection, this resulting promise will be rejected with the same rejection value.
+         *
+         * @param promises A hash of promises.
+         */
+        all(promises: { [id: string]: IPromise<any>; }): IPromise<{ [id: string]: any; }>;
+        all<T extends {}>(promises: { [id: string]: IPromise<any>; }): IPromise<T>;
+        /**
+         * Creates a Deferred object which represents a task which will finish in the future.
+         */
+        defer<T>(): IDeferred<T>;
+        /**
+         * Creates a promise that is resolved as rejected with the specified reason. This api should be used to forward rejection in a chain of promises. If you are dealing with the last promise in a promise chain, you don't need to worry about it.
+         *
+         * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of reject as the throw keyword in JavaScript. This also means that if you "catch" an error via a promise error callback and you want to forward the error to the promise derived from the current promise, you have to "rethrow" the error by returning a rejection constructed via reject.
+         *
+         * @param reason Constant, message, exception or an object representing the rejection reason.
+         */
+        reject(reason?: any): IPromise<any>;
+        /**
+         * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. This is useful when you are dealing with an object that might or might not be a promise, or if the promise comes from a source that can't be trusted.
+         *
+         * @param value Value or a promise
+         */
+        resolve<T>(value: IPromise<T>|T): IPromise<T>;
+        /**
+         * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. This is useful when you are dealing with an object that might or might not be a promise, or if the promise comes from a source that can't be trusted.
+         */
+        resolve(): IPromise<void>;
+        /**
+         * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. This is useful when you are dealing with an object that might or might not be a promise, or if the promise comes from a source that can't be trusted.
+         *
+         * @param value Value or a promise
+         */
+        when<T>(value: IPromise<T>|T): IPromise<T>;
+        /**
+         * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. This is useful when you are dealing with an object that might or might not be a promise, or if the promise comes from a source that can't be trusted.
+         */
+        when(): IPromise<void>;
+    }
+
+    interface IPromise<T> {
+        /**
+         * Regardless of when the promise was or will be resolved or rejected, then calls one of the success or error callbacks asynchronously as soon as the result is available. The callbacks are called with a single argument: the result or rejection reason. Additionally, the notify callback may be called zero or more times to provide a progress indication, before the promise is resolved or rejected.
+         * The successCallBack may return IPromise<void> for when a $q.reject() needs to be returned
+         * This method returns a new promise which is resolved or rejected via the return value of the successCallback, errorCallback. It also notifies via the return value of the notifyCallback method. The promise can not be resolved or rejected from the notifyCallback method.
+         */
+        then<TResult>(successCallback: (promiseValue: T) => IPromise<TResult>|TResult, errorCallback?: (reason: any) => any, notifyCallback?: (state: any) => any): IPromise<TResult>;
+
+        /**
+         * Shorthand for promise.then(null, errorCallback)
+         */
+        catch<TResult>(onRejected: (reason: any) => IPromise<TResult>|TResult): IPromise<TResult>;
+
+        /**
+         * Allows you to observe either the fulfillment or rejection of a promise, but to do so without modifying the final value. This is useful to release resources or do some clean-up that needs to be done whether the promise was rejected or resolved. See the full specification for more information.
+         *
+         * Because finally is a reserved word in JavaScript and reserved keywords are not supported as property names by ES3, you'll need to invoke the method like promise['finally'](callback) to make your code IE8 and Android 2.x compatible.
+         */
+        finally(finallyCallback: () => any): IPromise<T>;
+    }
+
+    interface IDeferred<T> {
+        resolve(value?: T|IPromise<T>): void;
+        reject(reason?: any): void;
+        notify(state?: any): void;
+        promise: IPromise<T>;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // AnchorScrollService
+    // see http://docs.angularjs.org/api/ng.$anchorScroll
+    ///////////////////////////////////////////////////////////////////////////
+    interface IAnchorScrollService {
+        (): void;
+        (hash: string): void;
+        yOffset: any;
+    }
+
+    interface IAnchorScrollProvider extends IServiceProvider {
+        disableAutoScrolling(): void;
+    }
+
+    /**
+     * $cacheFactory - service in module ng
+     *
+     * Factory that constructs Cache objects and gives access to them.
+     *
+     * see https://docs.angularjs.org/api/ng/service/$cacheFactory
+     */
+    interface ICacheFactoryService {
+        /**
+         * Factory that constructs Cache objects and gives access to them.
+         *
+         * @param cacheId Name or id of the newly created cache.
+         * @param optionsMap Options object that specifies the cache behavior. Properties:
+         *
+         * capacity — turns the cache into LRU cache.
+         */
+        (cacheId: string, optionsMap?: { capacity?: number; }): ICacheObject;
+
+        /**
+         * Get information about all the caches that have been created.
+         * @returns key-value map of cacheId to the result of calling cache#info
+         */
+        info(): any;
+
+        /**
+         * Get access to a cache object by the cacheId used when it was created.
+         *
+         * @param cacheId Name or id of a cache to access.
+         */
+        get(cacheId: string): ICacheObject;
+    }
+
+    /**
+     * $cacheFactory.Cache - type in module ng
+     *
+     * A cache object used to store and retrieve data, primarily used by $http and the script directive to cache templates and other data.
+     *
+     * see https://docs.angularjs.org/api/ng/type/$cacheFactory.Cache
+     */
+    interface ICacheObject {
+        /**
+         * Retrieve information regarding a particular Cache.
+         */
+        info(): {
+            /**
+             * the id of the cache instance
+             */
+            id: string;
+
+            /**
+             * the number of entries kept in the cache instance
+             */
+            size: number;
+
+            //...: any additional properties from the options object when creating the cache.
+        };
+
+        /**
+         * Inserts a named entry into the Cache object to be retrieved later, and incrementing the size of the cache if the key was not already present in the cache. If behaving like an LRU cache, it will also remove stale entries from the set.
+         *
+         * It will not insert undefined values into the cache.
+         *
+         * @param key the key under which the cached data is stored.
+         * @param value the value to store alongside the key. If it is undefined, the key will not be stored.
+         */
+        put<T>(key: string, value?: T): T;
+
+        /**
+         * Retrieves named data stored in the Cache object.
+         *
+         * @param key the key of the data to be retrieved
+         */
+        get<T>(key: string): T;
+
+        /**
+         * Removes an entry from the Cache object.
+         *
+         * @param key the key of the entry to be removed
+         */
+        remove(key: string): void;
+
+        /**
+         * Clears the cache object of any entries.
+         */
+        removeAll(): void;
+
+        /**
+         * Destroys the Cache object entirely, removing it from the $cacheFactory set.
+         */
+        destroy(): void;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // CompileService
+    // see http://docs.angularjs.org/api/ng.$compile
+    // see http://docs.angularjs.org/api/ng.$compileProvider
+    ///////////////////////////////////////////////////////////////////////////
+    interface ICompileService {
+        (element: string, transclude?: ITranscludeFunction, maxPriority?: number): ITemplateLinkingFunction;
+        (element: Element, transclude?: ITranscludeFunction, maxPriority?: number): ITemplateLinkingFunction;
+        (element: JQuery, transclude?: ITranscludeFunction, maxPriority?: number): ITemplateLinkingFunction;
+    }
+
+    interface ICompileProvider extends IServiceProvider {
+        directive(name: string, directiveFactory: Function): ICompileProvider;
+
+        // Undocumented, but it is there...
+        directive(directivesMap: any): ICompileProvider;
+
+        aHrefSanitizationWhitelist(): RegExp;
+        aHrefSanitizationWhitelist(regexp: RegExp): ICompileProvider;
+
+        imgSrcSanitizationWhitelist(): RegExp;
+        imgSrcSanitizationWhitelist(regexp: RegExp): ICompileProvider;
+
+        debugInfoEnabled(enabled?: boolean): any;
+    }
+
+    interface ICloneAttachFunction {
+        // Let's hint but not force cloneAttachFn's signature
+        (clonedElement?: JQuery, scope?: IScope): any;
+    }
+
+    // This corresponds to the "publicLinkFn" returned by $compile.
+    interface ITemplateLinkingFunction {
+        (scope: IScope, cloneAttachFn?: ICloneAttachFunction): IAugmentedJQuery;
+    }
+
+    // This corresponds to $transclude (and also the transclude function passed to link).
+    interface ITranscludeFunction {
+        // If the scope is provided, then the cloneAttachFn must be as well.
+        (scope: IScope, cloneAttachFn: ICloneAttachFunction): IAugmentedJQuery;
+        // If one argument is provided, then it's assumed to be the cloneAttachFn.
+        (cloneAttachFn?: ICloneAttachFunction): IAugmentedJQuery;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // ControllerService
+    // see http://docs.angularjs.org/api/ng.$controller
+    // see http://docs.angularjs.org/api/ng.$controllerProvider
+    ///////////////////////////////////////////////////////////////////////////
+    interface IControllerService {
+        // Although the documentation doesn't state this, locals are optional
+        <T>(controllerConstructor: new (...args: any[]) => T, locals?: any, bindToController?: any): T;
+        <T>(controllerConstructor: Function, locals?: any, bindToController?: any): T;
+        <T>(controllerName: string, locals?: any, bindToController?: any): T;
+    }
+
+    interface IControllerProvider extends IServiceProvider {
+        register(name: string, controllerConstructor: Function): void;
+        register(name: string, dependencyAnnotatedConstructor: any[]): void;
+        allowGlobals(): void;
+    }
+
+    /**
+     * HttpService
+     * see http://docs.angularjs.org/api/ng/service/$http
+     */
+    interface IHttpService {
+        /**
+         * Object describing the request to be made and how it should be processed.
+         */
+        <T>(config: IRequestConfig): IHttpPromise<T>;
+
+        /**
+         * Shortcut method to perform GET request.
+         *
+         * @param url Relative or absolute URL specifying the destination of the request
+         * @param config Optional configuration object
+         */
+        get<T>(url: string, config?: IRequestShortcutConfig): IHttpPromise<T>;
+
+        /**
+         * Shortcut method to perform DELETE request.
+         *
+         * @param url Relative or absolute URL specifying the destination of the request
+         * @param config Optional configuration object
+         */
+        delete<T>(url: string, config?: IRequestShortcutConfig): IHttpPromise<T>;
+
+        /**
+         * Shortcut method to perform HEAD request.
+         *
+         * @param url Relative or absolute URL specifying the destination of the request
+         * @param config Optional configuration object
+         */
+        head<T>(url: string, config?: IRequestShortcutConfig): IHttpPromise<T>;
+
+        /**
+         * Shortcut method to perform JSONP request.
+         *
+         * @param url Relative or absolute URL specifying the destination of the request
+         * @param config Optional configuration object
+         */
+        jsonp<T>(url: string, config?: IRequestShortcutConfig): IHttpPromise<T>;
+
+        /**
+         * Shortcut method to perform POST request.
+         *
+         * @param url Relative or absolute URL specifying the destination of the request
+         * @param data Request content
+         * @param config Optional configuration object
+         */
+        post<T>(url: string, data: any, config?: IRequestShortcutConfig): IHttpPromise<T>;
+
+        /**
+         * Shortcut method to perform PUT request.
+         *
+         * @param url Relative or absolute URL specifying the destination of the request
+         * @param data Request content
+         * @param config Optional configuration object
+         */
+        put<T>(url: string, data: any, config?: IRequestShortcutConfig): IHttpPromise<T>;
+
+        /**
+         * Shortcut method to perform PATCH request.
+         *
+         * @param url Relative or absolute URL specifying the destination of the request
+         * @param data Request content
+         * @param config Optional configuration object
+         */
+        patch<T>(url: string, data: any, config?: IRequestShortcutConfig): IHttpPromise<T>;
+
+        /**
+         * Runtime equivalent of the $httpProvider.defaults property. Allows configuration of default headers, withCredentials as well as request and response transformations.
+         */
+        defaults: IHttpProviderDefaults;
+
+        /**
+         * Array of config objects for currently pending requests. This is primarily meant to be used for debugging purposes.
+         */
+        pendingRequests: IRequestConfig[];
+    }
+
+    /**
+     * Object describing the request to be made and how it should be processed.
+     * see http://docs.angularjs.org/api/ng/service/$http#usage
+     */
+    interface IRequestShortcutConfig extends IHttpProviderDefaults {
+        /**
+         * {Object.<string|Object>}
+         * Map of strings or objects which will be turned to ?key1=value1&key2=value2 after the url. If the value is not a string, it will be JSONified.
+         */
+        params?: any;
+
+        /**
+         * {string|Object}
+         * Data to be sent as the request message data.
+         */
+        data?: any;
+
+        /**
+         * Timeout in milliseconds, or promise that should abort the request when resolved.
+         */
+        timeout?: number|IPromise<any>;
+
+        /**
+         * See [XMLHttpRequest.responseType]https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#xmlhttprequest-responsetype
+         */
+        responseType?: string;
+    }
+
+    /**
+     * Object describing the request to be made and how it should be processed.
+     * see http://docs.angularjs.org/api/ng/service/$http#usage
+     */
+    interface IRequestConfig extends IRequestShortcutConfig {
+        /**
+         * HTTP method (e.g. 'GET', 'POST', etc)
+         */
+        method: string;
+        /**
+         * Absolute or relative URL of the resource that is being requested.
+         */
+        url: string;
+    }
+
+    interface IHttpHeadersGetter {
+        (): { [name: string]: string; };
+        (headerName: string): string;
+    }
+
+    interface IHttpPromiseCallback<T> {
+        (data: T, status: number, headers: IHttpHeadersGetter, config: IRequestConfig): void;
+    }
+
+    interface IHttpPromiseCallbackArg<T> {
+        data?: T;
+        status?: number;
+        headers?: IHttpHeadersGetter;
+        config?: IRequestConfig;
+        statusText?: string;
+    }
+
+    interface IHttpPromise<T> extends IPromise<IHttpPromiseCallbackArg<T>> {
+        success(callback: IHttpPromiseCallback<T>): IHttpPromise<T>;
+        error(callback: IHttpPromiseCallback<any>): IHttpPromise<T>;
+    }
+
+    // See the jsdoc for transformData() at https://github.com/angular/angular.js/blob/master/src/ng/http.js#L228
+    interface IHttpRequestTransformer {
+        (data: any, headersGetter: IHttpHeadersGetter): any;
+    }
+
+    // The definition of fields are the same as IHttpPromiseCallbackArg
+    interface IHttpResponseTransformer {
+        (data: any, headersGetter: IHttpHeadersGetter, status: number): any;
+    }
+
+    interface IHttpRequestConfigHeaders {
+        [requestType: string]: string|(() => string);
+        common?: string|(() => string);
+        get?: string|(() => string);
+        post?: string|(() => string);
+        put?: string|(() => string);
+        patch?: string|(() => string);
+    }
+
+    /**
+    * Object that controls the defaults for $http provider. Not all fields of IRequestShortcutConfig can be configured
+    * via defaults and the docs do not say which. The following is based on the inspection of the source code.
+    * https://docs.angularjs.org/api/ng/service/$http#defaults
+    * https://docs.angularjs.org/api/ng/service/$http#usage
+    * https://docs.angularjs.org/api/ng/provider/$httpProvider The properties section
+    */
+    interface IHttpProviderDefaults {
+        /**
+         * {boolean|Cache}
+         * If true, a default $http cache will be used to cache the GET request, otherwise if a cache instance built with $cacheFactory, this cache will be used for caching.
+         */
+        cache?: any;
+
+        /**
+         * Transform function or an array of such functions. The transform function takes the http request body and
+         * headers and returns its transformed (typically serialized) version.
+         * @see {@link https://docs.angularjs.org/api/ng/service/$http#transforming-requests-and-responses}
+         */
+        transformRequest?: IHttpRequestTransformer |IHttpRequestTransformer[];
+
+        /**
+         * Transform function or an array of such functions. The transform function takes the http response body and
+         * headers and returns its transformed (typically deserialized) version.
+         */
+        transformResponse?: IHttpResponseTransformer | IHttpResponseTransformer[];
+
+        /**
+         * Map of strings or functions which return strings representing HTTP headers to send to the server. If the
+         * return value of a function is null, the header will not be sent.
+         * The key of the map is the request verb in lower case. The "common" key applies to all requests.
+         * @see {@link https://docs.angularjs.org/api/ng/service/$http#setting-http-headers}
+         */
+        headers?: IHttpRequestConfigHeaders;
+
+        /** Name of HTTP header to populate with the XSRF token. */
+        xsrfHeaderName?: string;
+
+        /** Name of cookie containing the XSRF token. */
+        xsrfCookieName?: string;
+
+        /**
+         * whether to to set the withCredentials flag on the XHR object. See [requests with credentials]https://developer.mozilla.org/en/http_access_control#section_5 for more information.
+         */
+        withCredentials?: boolean;
+
+        /**
+        * A function used to the prepare string representation of request parameters (specified as an object). If
+        * specified as string, it is interpreted as a function registered with the $injector. Defaults to
+        * $httpParamSerializer.
+        */
+        paramSerializer?: string | ((obj: any) => string);
+    }
+
+    interface IHttpInterceptor {
+        request?: (config: IRequestConfig) => IRequestConfig|IPromise<IRequestConfig>;
+        requestError?: (rejection: any) => any;
+        response?: <T>(response: IHttpPromiseCallbackArg<T>) => IPromise<T>|T;
+        responseError?: (rejection: any) => any;
+    }
+
+    interface IHttpInterceptorFactory {
+        (...args: any[]): IHttpInterceptor;
+    }
+
+    interface IHttpProvider extends IServiceProvider {
+        defaults: IHttpProviderDefaults;
+
+        /**
+         * Register service factories (names or implementations) for interceptors which are called before and after
+         * each request.
+         */
+        interceptors: (string|IHttpInterceptorFactory|(string|IHttpInterceptorFactory)[])[];
+        useApplyAsync(): boolean;
+        useApplyAsync(value: boolean): IHttpProvider;
+
+        /**
+         *
+         * @param {boolean=} value If true, `$http` will return a normal promise without the `success` and `error` methods.
+         * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining.
+         *    otherwise, returns the current configured value.
+         */
+        useLegacyPromiseExtensions(value:boolean) : boolean | IHttpProvider;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // HttpBackendService
+    // see http://docs.angularjs.org/api/ng.$httpBackend
+    // You should never need to use this service directly.
+    ///////////////////////////////////////////////////////////////////////////
+    interface IHttpBackendService {
+        // XXX Perhaps define callback signature in the future
+        (method: string, url: string, post?: any, callback?: Function, headers?: any, timeout?: number, withCredentials?: boolean): void;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // InterpolateService
+    // see http://docs.angularjs.org/api/ng.$interpolate
+    // see http://docs.angularjs.org/api/ng.$interpolateProvider
+    ///////////////////////////////////////////////////////////////////////////
+    interface IInterpolateService {
+        (text: string, mustHaveExpression?: boolean, trustedContext?: string, allOrNothing?: boolean): IInterpolationFunction;
+        endSymbol(): string;
+        startSymbol(): string;
+    }
+
+    interface IInterpolationFunction {
+        (context: any): string;
+    }
+
+    interface IInterpolateProvider extends IServiceProvider {
+        startSymbol(): string;
+        startSymbol(value: string): IInterpolateProvider;
+        endSymbol(): string;
+        endSymbol(value: string): IInterpolateProvider;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // TemplateCacheService
+    // see http://docs.angularjs.org/api/ng.$templateCache
+    ///////////////////////////////////////////////////////////////////////////
+    interface ITemplateCacheService extends ICacheObject {}
+
+    ///////////////////////////////////////////////////////////////////////////
+    // SCEService
+    // see http://docs.angularjs.org/api/ng.$sce
+    ///////////////////////////////////////////////////////////////////////////
+    interface ISCEService {
+        getTrusted(type: string, mayBeTrusted: any): any;
+        getTrustedCss(value: any): any;
+        getTrustedHtml(value: any): any;
+        getTrustedJs(value: any): any;
+        getTrustedResourceUrl(value: any): any;
+        getTrustedUrl(value: any): any;
+        parse(type: string, expression: string): (context: any, locals: any) => any;
+        parseAsCss(expression: string): (context: any, locals: any) => any;
+        parseAsHtml(expression: string): (context: any, locals: any) => any;
+        parseAsJs(expression: string): (context: any, locals: any) => any;
+        parseAsResourceUrl(expression: string): (context: any, locals: any) => any;
+        parseAsUrl(expression: string): (context: any, locals: any) => any;
+        trustAs(type: string, value: any): any;
+        trustAsHtml(value: any): any;
+        trustAsJs(value: any): any;
+        trustAsResourceUrl(value: any): any;
+        trustAsUrl(value: any): any;
+        isEnabled(): boolean;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // SCEProvider
+    // see http://docs.angularjs.org/api/ng.$sceProvider
+    ///////////////////////////////////////////////////////////////////////////
+    interface ISCEProvider extends IServiceProvider {
+        enabled(value: boolean): void;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // SCEDelegateService
+    // see http://docs.angularjs.org/api/ng.$sceDelegate
+    ///////////////////////////////////////////////////////////////////////////
+    interface ISCEDelegateService {
+        getTrusted(type: string, mayBeTrusted: any): any;
+        trustAs(type: string, value: any): any;
+        valueOf(value: any): any;
+    }
+
+
+    ///////////////////////////////////////////////////////////////////////////
+    // SCEDelegateProvider
+    // see http://docs.angularjs.org/api/ng.$sceDelegateProvider
+    ///////////////////////////////////////////////////////////////////////////
+    interface ISCEDelegateProvider extends IServiceProvider {
+        resourceUrlBlacklist(blacklist: any[]): void;
+        resourceUrlWhitelist(whitelist: any[]): void;
+        resourceUrlBlacklist(): any[];
+        resourceUrlWhitelist(): any[];
+    }
+
+    /**
+     * $templateRequest service
+     * see http://docs.angularjs.org/api/ng/service/$templateRequest
+     */
+    interface ITemplateRequestService {
+        /**
+         * Downloads a template using $http and, upon success, stores the
+         * contents inside of $templateCache.
+         *
+         * If the HTTP request fails or the response data of the HTTP request is
+         * empty then a $compile error will be thrown (unless
+         * {ignoreRequestError} is set to true).
+         *
+         * @param tpl                  The template URL.
+         * @param ignoreRequestError   Whether or not to ignore the exception
+         *                             when the request fails or the template is
+         *                             empty.
+         *
+         * @return   A promise whose value is the template content.
+         */
+        (tpl: string, ignoreRequestError?: boolean): IPromise<string>;
+        /**
+         * total amount of pending template requests being downloaded.
+         * @type {number}
+         */
+        totalPendingRequests: number;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Component
+    // see http://angularjs.blogspot.com.br/2015/11/angularjs-15-beta2-and-14-releases.html
+    // and http://toddmotto.com/exploring-the-angular-1-5-component-method/
+    ///////////////////////////////////////////////////////////////////////////
+    /**
+     * Runtime representation a type that a Component or other object is instances of.
+     *
+     * An example of a `Type` is `MyCustomComponent` class, which in JavaScript is be represented by
+     * the `MyCustomComponent` constructor function.
+     */
+    interface Type extends Function {
+    }
+
+    /**
+     * `RouteDefinition` defines a route within a {@link RouteConfig} decorator.
+     *
+     * Supported keys:
+     * - `path` or `aux` (requires exactly one of these)
+     * - `component`, `loader`,  `redirectTo` (requires exactly one of these)
+     * - `name` or `as` (optional) (requires exactly one of these)
+     * - `data` (optional)
+     *
+     * See also {@link Route}, {@link AsyncRoute}, {@link AuxRoute}, and {@link Redirect}.
+     */
+    interface RouteDefinition {
+        path?: string;
+        aux?: string;
+        component?: Type | ComponentDefinition | string;
+        loader?: Function;
+        redirectTo?: any[];
+        as?: string;
+        name?: string;
+        data?: any;
+        useAsDefault?: boolean;
+    }
+
+    /**
+     * Represents either a component type (`type` is `component`) or a loader function
+     * (`type` is `loader`).
+     *
+     * See also {@link RouteDefinition}.
+     */
+    interface ComponentDefinition {
+        type: string;
+        loader?: Function;
+        component?: Type;
+    }
+
+    /**
+     * Component definition object (a simplified directive definition object)
+     */
+    interface IComponentOptions {
+        /**
+         * Controller constructor function that should be associated with newly created scope or the name of a registered
+         * controller if passed as a string. Empty function by default.
+         */
+        controller?: string | Function;
+        /**
+         * An identifier name for a reference to the controller. If present, the controller will be published to scope under
+         * the controllerAs name. If not present, this will default to be the same as the component name.
+         */
+        controllerAs?: string;
+        /**
+         * html template as a string or a function that returns an html template as a string which should be used as the
+         * contents of this component. Empty string by default.
+         * If template is a function, then it is injected with the following locals:
+         * $element - Current element
+         * $attrs - Current attributes object for the element
+         */
+        template?: string | Function;
+        /**
+         * path or function that returns a path to an html template that should be used as the contents of this component.
+         * If templateUrl is a function, then it is injected with the following locals:
+         * $element - Current element
+         * $attrs - Current attributes object for the element
+         */
+        templateUrl?: string | Function;
+        /**
+         * Define DOM attribute binding to component properties. Component properties are always bound to the component
+         * controller and not to the scope.
+         */
+        bindings?: any;
+        /**
+         * Whether transclusion is enabled. Enabled by default.
+         */
+        transclude?: boolean;
+        /**
+         * Whether the new scope is isolated. Isolated by default.
+         */
+        isolate?: boolean;
+        /**
+         * String of subset of EACM which restricts the component to specific directive declaration style. If omitted,
+         * this defaults to 'E'.
+         */
+        restrict?: string;
+        $canActivate?: () => boolean;
+        $routeConfig?: RouteDefinition[];
+    }
+
+    interface IComponentTemplateFn {
+        ( $element?: IAugmentedJQuery, $attrs?: IAttributes ): string;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Directive
+    // see http://docs.angularjs.org/api/ng.$compileProvider#directive
+    // and http://docs.angularjs.org/guide/directive
+    ///////////////////////////////////////////////////////////////////////////
+
+    interface IDirectiveFactory {
+        (...args: any[]): IDirective;
+    }
+
+    interface IDirectiveLinkFn {
+        (
+            scope: IScope,
+            instanceElement: IAugmentedJQuery,
+            instanceAttributes: IAttributes,
+            controller: {},
+            transclude: ITranscludeFunction
+        ): void;
+    }
+
+    interface IDirectivePrePost {
+        pre?: IDirectiveLinkFn;
+        post?: IDirectiveLinkFn;
+    }
+
+    interface IDirectiveCompileFn {
+        (
+            templateElement: IAugmentedJQuery,
+            templateAttributes: IAttributes,
+            transclude: ITranscludeFunction
+        ): IDirectivePrePost;
+    }
+
+    interface IDirective {
+        compile?: IDirectiveCompileFn;
+        controller?: any;
+        controllerAs?: string;
+        bindToController?: boolean|Object;
+        link?: IDirectiveLinkFn | IDirectivePrePost;
+        name?: string;
+        priority?: number;
+        replace?: boolean;
+        require?: any;
+        restrict?: string;
+        scope?: any;
+        template?: any;
+        templateNamespace?: string;
+        templateUrl?: any;
+        terminal?: boolean;
+        transclude?: any;
+    }
+
+    /**
+     * angular.element
+     * when calling angular.element, angular returns a jQuery object,
+     * augmented with additional methods like e.g. scope.
+     * see: http://docs.angularjs.org/api/angular.element
+     */
+    interface IAugmentedJQueryStatic extends JQueryStatic {
+        (selector: string, context?: any): IAugmentedJQuery;
+        (element: Element): IAugmentedJQuery;
+        (object: {}): IAugmentedJQuery;
+        (elementArray: Element[]): IAugmentedJQuery;
+        (object: JQuery): IAugmentedJQuery;
+        (func: Function): IAugmentedJQuery;
+        (array: any[]): IAugmentedJQuery;
+        (): IAugmentedJQuery;
+    }
+
+    interface IAugmentedJQuery extends JQuery {
+        // TODO: events, how to define?
+        //$destroy
+
+        find(selector: string): IAugmentedJQuery;
+        find(element: any): IAugmentedJQuery;
+        find(obj: JQuery): IAugmentedJQuery;
+        controller(): any;
+        controller(name: string): any;
+        injector(): any;
+        scope(): IScope;
+        isolateScope(): IScope;
+
+        inheritedData(key: string, value: any): JQuery;
+        inheritedData(obj: { [key: string]: any; }): JQuery;
+        inheritedData(key?: string): any;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // AUTO module (angular.js)
+    ///////////////////////////////////////////////////////////////////////////
+    export module auto {
+
+        ///////////////////////////////////////////////////////////////////////
+        // InjectorService
+        // see http://docs.angularjs.org/api/AUTO.$injector
+        ///////////////////////////////////////////////////////////////////////
+        interface IInjectorService {
+            annotate(fn: Function): string[];
+            annotate(inlineAnnotatedFunction: any[]): string[];
+            get<T>(name: string, caller?: string): T;
+            has(name: string): boolean;
+            instantiate<T>(typeConstructor: Function, locals?: any): T;
+            invoke(inlineAnnotatedFunction: any[]): any;
+            invoke(func: Function, context?: any, locals?: any): any;
+        }
+
+        ///////////////////////////////////////////////////////////////////////
+        // ProvideService
+        // see http://docs.angularjs.org/api/AUTO.$provide
+        ///////////////////////////////////////////////////////////////////////
+        interface IProvideService {
+            // Documentation says it returns the registered instance, but actual
+            // implementation does not return anything.
+            // constant(name: string, value: any): any;
+            /**
+             * Register a constant service, such as a string, a number, an array, an object or a function, with the $injector. Unlike value it can be injected into a module configuration function (see config) and it cannot be overridden by an Angular decorator.
+             *
+             * @param name The name of the constant.
+             * @param value The constant value.
+             */
+            constant(name: string, value: any): void;
+
+            /**
+             * Register a service decorator with the $injector. A service decorator intercepts the creation of a service, allowing it to override or modify the behaviour of the service. The object returned by the decorator may be the original service, or a new service object which replaces or wraps and delegates to the original service.
+             *
+             * @param name The name of the service to decorate.
+             * @param decorator This function will be invoked when the service needs to be instantiated and should return the decorated service instance. The function is called using the injector.invoke method and is therefore fully injectable. Local injection arguments:
+             *
+             * $delegate - The original service instance, which can be monkey patched, configured, decorated or delegated to.
+             */
+            decorator(name: string, decorator: Function): void;
+            /**
+             * Register a service decorator with the $injector. A service decorator intercepts the creation of a service, allowing it to override or modify the behaviour of the service. The object returned by the decorator may be the original service, or a new service object which replaces or wraps and delegates to the original service.
+             *
+             * @param name The name of the service to decorate.
+             * @param inlineAnnotatedFunction This function will be invoked when the service needs to be instantiated and should return the decorated service instance. The function is called using the injector.invoke method and is therefore fully injectable. Local injection arguments:
+             *
+             * $delegate - The original service instance, which can be monkey patched, configured, decorated or delegated to.
+             */
+            decorator(name: string, inlineAnnotatedFunction: any[]): void;
+            factory(name: string, serviceFactoryFunction: Function): IServiceProvider;
+            factory(name: string, inlineAnnotatedFunction: any[]): IServiceProvider;
+            provider(name: string, provider: IServiceProvider): IServiceProvider;
+            provider(name: string, serviceProviderConstructor: Function): IServiceProvider;
+            service(name: string, constructor: Function): IServiceProvider;
+            service(name: string, inlineAnnotatedFunction: any[]): IServiceProvider;
+            value(name: string, value: any): IServiceProvider;
+        }
+
+    }
+}

+ 80 - 0
.vscode/typings/cordova/cordova.d.ts

@@ -0,0 +1,80 @@
+// Type definitions for Apache Cordova
+// Project: http://cordova.apache.org
+// Definitions by: Microsoft Open Technologies Inc. <http://msopentech.com>
+// Definitions: https://github.com/borisyankov/DefinitelyTyped
+//
+// Copyright (c) Microsoft Open Technologies, Inc.
+// Licensed under the MIT license.
+
+interface Cordova {
+    /** Invokes native functionality by specifying corresponding service name, action and optional parameters.
+     * @param success A success callback function.
+     * @param fail An error callback function.
+     * @param service The service name to call on the native side (corresponds to a native class).
+     * @param action The action name to call on the native side (generally corresponds to the native class method).
+     * @param args An array of arguments to pass into the native environment.
+     */
+    exec(success: () => any, fail: () => any, service: string, action: string, args?: string[]): void;
+    /** Gets the operating system name. */
+    platformId: string;
+    /** Gets Cordova framework version */
+    version: string;
+    /** Defines custom logic as a Cordova module. Other modules can later access it using module name provided. */
+    define(moduleName: string, factory: (require: any, exports: any, module: any) => any): void;
+    /** Access a Cordova module by name. */
+    require(moduleName: string): any;
+    /** Namespace for Cordova plugin functionality */
+    plugins:CordovaPlugins;
+}
+
+interface CordovaPlugins {}
+
+interface Document {
+    addEventListener(type: "deviceready", listener: (ev: Event) => any, useCapture?: boolean): void;
+    addEventListener(type: "pause", listener: (ev: Event) => any, useCapture?: boolean): void;
+    addEventListener(type: "resume", listener: (ev: Event) => any, useCapture?: boolean): void;
+    addEventListener(type: "backbutton", listener: (ev: Event) => any, useCapture?: boolean): void;
+    addEventListener(type: "menubutton", listener: (ev: Event) => any, useCapture?: boolean): void;
+    addEventListener(type: "searchbutton", listener: (ev: Event) => any, useCapture?: boolean): void;
+    addEventListener(type: "startcallbutton", listener: (ev: Event) => any, useCapture?: boolean): void;
+    addEventListener(type: "endcallbutton", listener: (ev: Event) => any, useCapture?: boolean): void;
+    addEventListener(type: "volumedownbutton", listener: (ev: Event) => any, useCapture?: boolean): void;
+    addEventListener(type: "volumeupbutton", listener: (ev: Event) => any, useCapture?: boolean): void;
+
+    removeEventListener(type: "deviceready", listener: (ev: Event) => any, useCapture?: boolean): void;
+    removeEventListener(type: "pause", listener: (ev: Event) => any, useCapture?: boolean): void;
+    removeEventListener(type: "resume", listener: (ev: Event) => any, useCapture?: boolean): void;
+    removeEventListener(type: "backbutton", listener: (ev: Event) => any, useCapture?: boolean): void;
+    removeEventListener(type: "menubutton", listener: (ev: Event) => any, useCapture?: boolean): void;
+    removeEventListener(type: "searchbutton", listener: (ev: Event) => any, useCapture?: boolean): void;
+    removeEventListener(type: "startcallbutton", listener: (ev: Event) => any, useCapture?: boolean): void;
+    removeEventListener(type: "endcallbutton", listener: (ev: Event) => any, useCapture?: boolean): void;
+    removeEventListener(type: "volumedownbutton", listener: (ev: Event) => any, useCapture?: boolean): void;
+    removeEventListener(type: "volumeupbutton", listener: (ev: Event) => any, useCapture?: boolean): void;
+
+    addEventListener(type: string, listener: (ev: Event) => any, useCapture?: boolean): void;
+    removeEventListener(type: string, listener: (ev: Event) => any, useCapture?: boolean): void;
+}
+
+interface Window {
+  cordova:Cordova;
+}
+
+// cordova/argscheck module
+interface ArgsCheck {
+    checkArgs(argsSpec: string, functionName: string, args: any[], callee?: any): void;
+    getValue(value?: any, defaultValue?: any): any;
+    enableChecks: boolean;
+}
+
+// cordova/urlutil module
+interface UrlUtil {
+    makeAbsolute(url: string): string
+}
+
+/** Apache Cordova instance */
+declare var cordova: Cordova;
+
+declare module 'cordova' {
+    export = cordova;
+}

+ 391 - 0
.vscode/typings/ionic/ionic.d.ts

@@ -0,0 +1,391 @@
+// Type definitions for Ionic
+// Project: http://ionicframework.com
+// Definitions by: Spencer Williams <https://github.com/spencerwi/>
+// Definitions: https://github.com/borisyankov/DefinitelyTyped
+
+/// <reference path="../angularjs/angular.d.ts" />
+
+interface IonicStatic {
+    /**
+     * What Ionic package version is.
+     */
+    version: string;
+    Platform: {
+        /**
+        * Trigger a callback once the device is ready, or immediately
+         * if the device is already ready. This method can be run from
+         * anywhere and does not need to be wrapped by any additonal methods.
+         * When the app is within a WebView (Cordova), it’ll fire
+         * the callback once the device is ready. If the app is within
+         * a web browser, it’ll fire the callback after window.load.
+         * Please remember that Cordova features (Camera, FileSystem, etc) still
+         * will not work in a web browser.
+         */
+        ready(callback: ()=>any): void;
+        /**
+         * Set the grade of the device: ‘a’, ‘b’, or ‘c’. ‘a’ is the best
+         * (most css features enabled), ‘c’ is the worst. By default, sets the grade
+         * depending on the current device.
+         */
+        setGrade(grade: string): void;
+        /**
+         * Return the current device (given by cordova).
+         */
+        device(): any;
+        /**
+         * Check if we are running within a WebView (such as Cordova).
+         */
+        isWebView(): boolean;
+        /**
+         * Whether we are running on iPad.
+         */
+        isIPad(): boolean;
+        /**
+         * Whether we are running on iOS.
+         */
+        isIOS(): boolean;
+        /**
+         * Whether we are running on Android.
+         */
+        isAndroid(): boolean;
+        /**
+         * Whether we are running on Windows Phone.
+         */
+        isWindowsPhone(): boolean;
+        /**
+         * The name of the current platform.
+         */
+        platform(): string;
+        /**
+         * The version of the current device platform.
+         */
+        version(): number;
+        /**
+         * Exit the app.
+         */
+        exitApp(): void;
+        /**
+         * Shows or hides the device status bar (in Cordova). Requires cordova plugin add org.apache.cordova.statusbar
+         */
+        showStatusBar(shouldShow: boolean): void;
+        /**
+         * Sets whether the app is fullscreen or not (in Cordova).
+         */
+        fullScreen(showFullScreen?: boolean, showStatusBar?: boolean): void;
+        /**
+         * Whether the device is ready.
+         */
+        isReady: boolean;
+        /**
+         * Whether the device is fullscreen.
+         */
+        isFullScreen: boolean;
+        /**
+         * An array of all platforms found.
+         */
+        platforms: Array<string>;
+        /**
+         * What grade the current platform is.
+         */
+        grade: string;
+    };
+}
+
+declare var ionic: IonicStatic;
+
+declare module 'ionic' {
+    export = ionic;
+}
+
+declare module ionic {
+    module actionSheet {
+        interface IonicActionSheetService {
+            show(options: IonicActionSheetOptions): ()=>void;
+        }
+        interface IonicActionSheetButton {
+            text: string;
+        }
+        interface IonicActionSheetOptions {
+            buttons?: Array<IonicActionSheetButton>;
+            titleText?: string;
+            cancelText?: string;
+            destructiveText?: string;
+            cancel?: ()=>any;
+            buttonClicked?: (index: number)=>boolean;
+            destructiveButtonClicked?: ()=>boolean;
+            cancelOnStateChange?: boolean;
+            cssClass?: string;
+        }
+    }
+    module backdrop {
+        interface IonicBackdropService {
+            retain(): void;
+            release(): void;
+        }
+    }
+    module gestures {
+        interface IonicGestureService {
+            on(eventType: string, callback: (e: any)=>any, $element: angular.IAugmentedJQuery, options: any): IonicGesture;
+            off(gesture: IonicGesture, eventType: string, callback: (e: any)=>any): void;
+        }
+
+        interface IonicGesture {
+            element: Element;
+            enabled: boolean;
+            options: {stop_browser_behavior: string };
+            on(gesture: string, handler: Function): IonicGesture;
+            off(gesture: string, handler: Function): IonicGesture;
+            trigger(gesture: string, eventData: any): IonicGesture;
+            enable(state: boolean): IonicGesture;
+        }
+
+    }
+    module list {
+        interface IonicListDelegate {
+            showReorder(showReorder?: boolean): boolean;
+            showDelete(showDelete?: boolean): boolean;
+            canSwipeItems(canSwipeItems?: boolean): boolean;
+            closeOptionButtons(): void;
+            $getByHandle(handle: string): IonicListDelegate;
+        }
+    }
+    module loading {
+        interface IonicLoadingService {
+            show(opts?: IonicLoadingOptions): void;
+            hide(): void;
+        }
+        interface IonicLoadingOptions {
+            template?: string;
+            templateUrl?: string;
+            scope?: any;
+            noBackdrop?: boolean;
+            hideOnStateChange?: boolean;
+            delay?: number;
+            duration?: number;
+        }
+    }
+    module modal {
+        interface IonicModalService {
+            fromTemplate(templateString: string, options?: IonicModalOptions): IonicModalController;
+            fromTemplateUrl(templateUrl: string, options?: IonicModalOptions): angular.IPromise<IonicModalController>;
+        }
+
+        interface IonicModalController {
+            initialize(options: IonicModalOptions): void;
+            show(): angular.IPromise<void>;
+            hide(): angular.IPromise<void>;
+            remove(): angular.IPromise<void>;
+            isShown(): boolean;
+        }
+
+        interface IonicModalOptions {
+            scope?: any;
+            animation?: string;
+            focusFirstInput?: boolean;
+            backdropClickToClose?: boolean;
+            hardwareBackButtonClose?: boolean;
+        }
+    }
+    module navigation {
+        interface IonicNavBarDelegate {
+            align(direction?: string): void;
+            showBackButton(show?: boolean): boolean;
+            showBar(show?: boolean): boolean;
+            title(title: string): void;
+        }
+
+        interface IonicHistoryService {
+            viewHistory(): any;
+
+            currentView(): any;
+            currentHistoryId(): string;
+            currentTitle(val?: string): string;
+
+            backView(): any;
+            backTitle(): string;
+
+            forwardView(): any;
+
+            currentStateName(): string;
+
+            goBack(backCount?: number): void;
+            clearHistory(): void;
+            clearCache(): angular.IPromise<any>;
+            nextViewOptions(options: IonicHistoryNextViewOptions): void;
+        }
+        interface IonicHistoryNextViewOptions {
+            disableAnimate?: boolean;
+            disableBack?: boolean;
+            historyRoot?: boolean;
+        }
+    }
+    module platform {
+        interface IonicPlatformService {
+            onHardwareBackButton(callback: Function): void;
+            offHardwareBackButton(callback: Function): void;
+            registerBackButtonAction(callback: Function, priority: number, actionId?: any): Function;
+            on(type: string, callback: Function): Function;
+            ready(callback?: Function): angular.IPromise<any>;
+        }
+    }
+    module popover {
+        interface IonicPopoverService {
+            fromTemplate(templateString: string, options: IonicPopoverOptions): IonicPopoverController;
+            fromTemplateUrl(templateUrl: string, options: IonicPopoverOptions): angular.IPromise<IonicPopoverController>;
+        }
+        interface IonicPopoverController {
+            initialize(options: IonicPopoverOptions): void;
+            show($event?: any): angular.IPromise<any>;
+            hide(): angular.IPromise<any>;
+            isShown(): boolean;
+            remove(): angular.IPromise<any>;
+        }
+        interface IonicPopoverOptions {
+            scope?: any;
+            focusFirstInput?: boolean;
+            backdropClickToClose?: boolean;
+            hardwareBackButtonClose?: boolean;
+        }
+    }
+    module popup {
+        interface IonicPopupService {
+            show(options: IonicPopupFullOptions): IonicPopupPromise;
+            alert(options: IonicPopupAlertOptions): IonicPopupPromise;
+            confirm(options: IonicPopupConfirmOptions): IonicPopupConfirmPromise;
+            prompt(options: IonicPopupPromptOptions): IonicPopupPromise;
+        }
+
+        interface IonicPopupConfirmPromise extends angular.IPromise<boolean> {
+            close(value?: boolean): void;
+        }
+        interface IonicPopupPromise extends angular.IPromise<any> {
+            close(value?: any): any;
+        }
+        interface IonicPopupBaseOptions {
+            title?: string;
+            cssClass?: string;
+            subTitle?: string;
+            template?: string;
+            templateUrl?: string;
+        }
+        interface IonicPopupFullOptions extends IonicPopupBaseOptions {
+            scope?: any;
+            buttons?: Array<IonicPopupButton>;
+        }
+        interface IonicPopupButton {
+            text: string;
+            type?: string;
+            onTap?(event?: any): void;
+        }
+        interface IonicPopupAlertOptions extends IonicPopupBaseOptions {
+            okText?: string;
+            okType?: string;
+        }
+        interface IonicPopupConfirmOptions extends IonicPopupBaseOptions {
+            cancelText?: string;
+            cancelType?: string;
+            okText?: string;
+            okType?: string;
+        }
+        interface IonicPopupPromptOptions extends IonicPopupBaseOptions {
+            inputType?: string;
+            inputPlaceholder?: string;
+            cancelText?: string;
+            cancelType?: string;
+            okText?: string;
+            okType?: string;
+        }
+    }
+    module scroll {
+        interface IonicScrollDelegate {
+            resize(): void;
+            scrollTop(shouldAnimate?: boolean): void;
+            scrollBottom(shouldAnimate?: boolean): void;
+            scrollTo(left: number, top: number, shouldAnimate?: boolean): void;
+            scrollBy(left: number, top: number, shouldAnimate?: boolean): void;
+            zoomTo(level: number, animate?: boolean, originLeft?: number, originTop?: number): void;
+            zoomBy(factor: number, animate?: boolean, originLeft?: number, originTop?: number): void;
+            getScrollPosition(): {left: number; top: number};
+            anchorScroll(shouldAnimate?: boolean): void;
+            freezeScroll(shouldFreeze?: boolean): boolean;
+            freezeAllScrolls(shouldFreeze?: boolean): boolean;
+            getScrollView(): any;
+            $getByHandle(handle: string): IonicScrollDelegate;
+        }
+    }
+    module sideMenu {
+        interface IonicSideMenuDelegate {
+            toggleLeft(isOpen?: boolean): void;
+            toggleRight(isOpen?: boolean): void;
+            getOpenRatio(): number;
+            isOpen(): boolean;
+            isOpenLeft(): boolean;
+            isOpenRight(): boolean;
+            canDragContent(canDrag?: boolean): boolean;
+            edgeDragThreshold(value?: boolean|number): boolean;
+            $getByHandle(handle: string): IonicSideMenuDelegate;
+        }
+    }
+    module slideBox {
+        interface IonicSlideBoxDelegate {
+            update(): void;
+            slide(to: number, speed?: number): void;
+            enableSlide(shouldEnable?: boolean): boolean;
+            previous(speed?: number): void;
+            next(speed?: number): void;
+            stop(): void;
+            start(): void;
+            currentIndex(): number;
+            slidesCount(): number;
+            $getByHandle(handle: string): IonicSlideBoxDelegate;
+        }
+    }
+    module tabs {
+        interface IonicTabsDelegate {
+            select(index: number): void;
+            selectedIndex(): number;
+            $getByHandle(handle: string): IonicTabsDelegate;
+            showBar(show?: boolean): boolean;
+        }
+    }
+    module utility {
+        interface IonicConfigProvider {
+            views: {
+                transition(transition?: string): string;
+                maxCache(maxNumber?: number): number;
+                forwardCache(value?: boolean): boolean;
+            };
+            scrolling: {
+                jsScrolling(value?: boolean): boolean;
+            };
+            backButton: {
+                icon(value?: string): string;
+                text(value?: string): string;
+                previousTitleText(value?: boolean): boolean;
+            };
+            form: {
+                checkbox(value?: string): string;
+                toggle(value?: string): string;
+            };
+            spinner: {
+                icon(value?: string): string;
+            };
+            tabs: {
+                style(value?: string): string;
+                position(value?: string): string;
+            };
+            templates: {
+                maxPrefetch(value?: number): number;
+            };
+            navBar: {
+                alignTitle(value?: string): string;
+                positionPrimaryButtons(value?: string): string;
+                positionSecondaryButtons(value?: string): string;
+            };
+        }
+        interface IonicPositionService {
+            position(element: any): {top: number; left: number; width: number; height: number};
+            offset(element: any): {top: number; left: number; width: number; height: number};
+        }
+    }
+}

+ 3208 - 0
.vscode/typings/jquery/jquery.d.ts

@@ -0,0 +1,3208 @@
+// Type definitions for jQuery 1.10.x / 2.0.x
+// Project: http://jquery.com/
+// Definitions by: Boris Yankov <https://github.com/borisyankov/>, Christian Hoffmeister <https://github.com/choffmeister>, Steve Fenton <https://github.com/Steve-Fenton>, Diullei Gomes <https://github.com/Diullei>, Tass Iliopoulos <https://github.com/tasoili>, Jason Swearingen <https://github.com/jasons-novaleaf>, Sean Hill <https://github.com/seanski>, Guus Goossens <https://github.com/Guuz>, Kelly Summerlin <https://github.com/ksummerlin>, Basarat Ali Syed <https://github.com/basarat>, Nicholas Wolverson <https://github.com/nwolverson>, Derek Cicerone <https://github.com/derekcicerone>, Andrew Gaspar <https://github.com/AndrewGaspar>, James Harrison Fisher <https://github.com/jameshfisher>, Seikichi Kondo <https://github.com/seikichi>, Benjamin Jackman <https://github.com/benjaminjackman>, Poul Sorensen <https://github.com/s093294>, Josh Strobl <https://github.com/JoshStrobl>, John Reilly <https://github.com/johnnyreilly/>, Dick van den Brink <https://github.com/DickvdBrink>
+// Definitions: https://github.com/borisyankov/DefinitelyTyped
+
+/* *****************************************************************************
+Copyright (c) Microsoft Corporation. All rights reserved.
+Licensed under the Apache License, Version 2.0 (the "License"); you may not use
+this file except in compliance with the License. You may obtain a copy of the
+License at http://www.apache.org/licenses/LICENSE-2.0
+THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
+WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
+MERCHANTABLITY OR NON-INFRINGEMENT.
+See the Apache Version 2.0 License for specific language governing permissions
+and limitations under the License.
+***************************************************************************** */
+
+
+/**
+ * Interface for the AJAX setting that will configure the AJAX request
+ */
+interface JQueryAjaxSettings {
+    /**
+     * The content type sent in the request header that tells the server what kind of response it will accept in return. If the accepts setting needs modification, it is recommended to do so once in the $.ajaxSetup() method.
+     */
+    accepts?: any;
+    /**
+     * By default, all requests are sent asynchronously (i.e. this is set to true by default). If you need synchronous requests, set this option to false. Cross-domain requests and dataType: "jsonp" requests do not support synchronous operation. Note that synchronous requests may temporarily lock the browser, disabling any actions while the request is active. As of jQuery 1.8, the use of async: false with jqXHR ($.Deferred) is deprecated; you must use the success/error/complete callback options instead of the corresponding methods of the jqXHR object such as jqXHR.done() or the deprecated jqXHR.success().
+     */
+    async?: boolean;
+    /**
+     * A pre-request callback function that can be used to modify the jqXHR (in jQuery 1.4.x, XMLHTTPRequest) object before it is sent. Use this to set custom headers, etc. The jqXHR and settings objects are passed as arguments. This is an Ajax Event. Returning false in the beforeSend function will cancel the request. As of jQuery 1.5, the beforeSend option will be called regardless of the type of request.
+     */
+    beforeSend? (jqXHR: JQueryXHR, settings: JQueryAjaxSettings): any;
+    /**
+     * If set to false, it will force requested pages not to be cached by the browser. Note: Setting cache to false will only work correctly with HEAD and GET requests. It works by appending "_={timestamp}" to the GET parameters. The parameter is not needed for other types of requests, except in IE8 when a POST is made to a URL that has already been requested by a GET.
+     */
+    cache?: boolean;
+    /**
+     * A function to be called when the request finishes (after success and error callbacks are executed). The function gets passed two arguments: The jqXHR (in jQuery 1.4.x, XMLHTTPRequest) object and a string categorizing the status of the request ("success", "notmodified", "error", "timeout", "abort", or "parsererror"). As of jQuery 1.5, the complete setting can accept an array of functions. Each function will be called in turn. This is an Ajax Event.
+     */
+    complete? (jqXHR: JQueryXHR, textStatus: string): any;
+    /**
+     * An object of string/regular-expression pairs that determine how jQuery will parse the response, given its content type. (version added: 1.5)
+     */
+    contents?: { [key: string]: any; };
+    //According to jQuery.ajax source code, ajax's option actually allows contentType to set to "false"
+    // https://github.com/borisyankov/DefinitelyTyped/issues/742
+    /**
+     * When sending data to the server, use this content type. Default is "application/x-www-form-urlencoded; charset=UTF-8", which is fine for most cases. If you explicitly pass in a content-type to $.ajax(), then it is always sent to the server (even if no data is sent). The W3C XMLHttpRequest specification dictates that the charset is always UTF-8; specifying another charset will not force the browser to change the encoding.
+     */
+    contentType?: any;
+    /**
+     * This object will be made the context of all Ajax-related callbacks. By default, the context is an object that represents the ajax settings used in the call ($.ajaxSettings merged with the settings passed to $.ajax).
+     */
+    context?: any;
+    /**
+     * An object containing dataType-to-dataType converters. Each converter's value is a function that returns the transformed value of the response. (version added: 1.5)
+     */
+    converters?: { [key: string]: any; };
+    /**
+     * If you wish to force a crossDomain request (such as JSONP) on the same domain, set the value of crossDomain to true. This allows, for example, server-side redirection to another domain. (version added: 1.5)
+     */
+    crossDomain?: boolean;
+    /**
+     * Data to be sent to the server. It is converted to a query string, if not already a string. It's appended to the url for GET-requests. See processData option to prevent this automatic processing. Object must be Key/Value pairs. If value is an Array, jQuery serializes multiple values with same key based on the value of the traditional setting (described below).
+     */
+    data?: any;
+    /**
+     * A function to be used to handle the raw response data of XMLHttpRequest.This is a pre-filtering function to sanitize the response. You should return the sanitized data. The function accepts two arguments: The raw data returned from the server and the 'dataType' parameter.
+     */
+    dataFilter? (data: any, ty: any): any;
+    /**
+     * The type of data that you're expecting back from the server. If none is specified, jQuery will try to infer it based on the MIME type of the response (an XML MIME type will yield XML, in 1.4 JSON will yield a JavaScript object, in 1.4 script will execute the script, and anything else will be returned as a string). 
+     */
+    dataType?: string;
+    /**
+     * A function to be called if the request fails. The function receives three arguments: The jqXHR (in jQuery 1.4.x, XMLHttpRequest) object, a string describing the type of error that occurred and an optional exception object, if one occurred. Possible values for the second argument (besides null) are "timeout", "error", "abort", and "parsererror". When an HTTP error occurs, errorThrown receives the textual portion of the HTTP status, such as "Not Found" or "Internal Server Error." As of jQuery 1.5, the error setting can accept an array of functions. Each function will be called in turn. Note: This handler is not called for cross-domain script and cross-domain JSONP requests. This is an Ajax Event.
+     */
+    error? (jqXHR: JQueryXHR, textStatus: string, errorThrown: string): any;
+    /**
+     * Whether to trigger global Ajax event handlers for this request. The default is true. Set to false to prevent the global handlers like ajaxStart or ajaxStop from being triggered. This can be used to control various Ajax Events.
+     */
+    global?: boolean;
+    /**
+     * An object of additional header key/value pairs to send along with requests using the XMLHttpRequest transport. The header X-Requested-With: XMLHttpRequest is always added, but its default XMLHttpRequest value can be changed here. Values in the headers setting can also be overwritten from within the beforeSend function. (version added: 1.5)
+     */
+    headers?: { [key: string]: any; };
+    /**
+     * Allow the request to be successful only if the response has changed since the last request. This is done by checking the Last-Modified header. Default value is false, ignoring the header. In jQuery 1.4 this technique also checks the 'etag' specified by the server to catch unmodified data.
+     */
+    ifModified?: boolean;
+    /**
+     * Allow the current environment to be recognized as "local," (e.g. the filesystem), even if jQuery does not recognize it as such by default. The following protocols are currently recognized as local: file, *-extension, and widget. If the isLocal setting needs modification, it is recommended to do so once in the $.ajaxSetup() method. (version added: 1.5.1)
+     */
+    isLocal?: boolean;
+    /**
+     * Override the callback function name in a jsonp request. This value will be used instead of 'callback' in the 'callback=?' part of the query string in the url. So {jsonp:'onJSONPLoad'} would result in 'onJSONPLoad=?' passed to the server. As of jQuery 1.5, setting the jsonp option to false prevents jQuery from adding the "?callback" string to the URL or attempting to use "=?" for transformation. In this case, you should also explicitly set the jsonpCallback setting. For example, { jsonp: false, jsonpCallback: "callbackName" }
+     */
+    jsonp?: any;
+    /**
+     * Specify the callback function name for a JSONP request. This value will be used instead of the random name automatically generated by jQuery. It is preferable to let jQuery generate a unique name as it'll make it easier to manage the requests and provide callbacks and error handling. You may want to specify the callback when you want to enable better browser caching of GET requests. As of jQuery 1.5, you can also use a function for this setting, in which case the value of jsonpCallback is set to the return value of that function.
+     */
+    jsonpCallback?: any;
+    /**
+     * The HTTP method to use for the request (e.g. "POST", "GET", "PUT"). (version added: 1.9.0)
+     */
+    method?: string;
+    /**
+     * A mime type to override the XHR mime type. (version added: 1.5.1)
+     */
+    mimeType?: string;
+    /**
+     * A password to be used with XMLHttpRequest in response to an HTTP access authentication request.
+     */
+    password?: string;
+    /**
+     * By default, data passed in to the data option as an object (technically, anything other than a string) will be processed and transformed into a query string, fitting to the default content-type "application/x-www-form-urlencoded". If you want to send a DOMDocument, or other non-processed data, set this option to false.
+     */
+    processData?: boolean;
+    /**
+     * Only applies when the "script" transport is used (e.g., cross-domain requests with "jsonp" or "script" dataType and "GET" type). Sets the charset attribute on the script tag used in the request. Used when the character set on the local page is not the same as the one on the remote script.
+     */
+    scriptCharset?: string;
+    /**
+     * An object of numeric HTTP codes and functions to be called when the response has the corresponding code. f the request is successful, the status code functions take the same parameters as the success callback; if it results in an error (including 3xx redirect), they take the same parameters as the error callback. (version added: 1.5)
+     */
+    statusCode?: { [key: string]: any; };
+    /**
+     * A function to be called if the request succeeds. The function gets passed three arguments: The data returned from the server, formatted according to the dataType parameter; a string describing the status; and the jqXHR (in jQuery 1.4.x, XMLHttpRequest) object. As of jQuery 1.5, the success setting can accept an array of functions. Each function will be called in turn. This is an Ajax Event.
+     */
+    success? (data: any, textStatus: string, jqXHR: JQueryXHR): any;
+    /**
+     * Set a timeout (in milliseconds) for the request. This will override any global timeout set with $.ajaxSetup(). The timeout period starts at the point the $.ajax call is made; if several other requests are in progress and the browser has no connections available, it is possible for a request to time out before it can be sent. In jQuery 1.4.x and below, the XMLHttpRequest object will be in an invalid state if the request times out; accessing any object members may throw an exception. In Firefox 3.0+ only, script and JSONP requests cannot be cancelled by a timeout; the script will run even if it arrives after the timeout period.
+     */
+    timeout?: number;
+    /**
+     * Set this to true if you wish to use the traditional style of param serialization.
+     */
+    traditional?: boolean;
+    /**
+     * The type of request to make ("POST" or "GET"), default is "GET". Note: Other HTTP request methods, such as PUT and DELETE, can also be used here, but they are not supported by all browsers.
+     */
+    type?: string;
+    /**
+     * A string containing the URL to which the request is sent.
+     */
+    url?: string;
+    /**
+     * A username to be used with XMLHttpRequest in response to an HTTP access authentication request.
+     */
+    username?: string;
+    /**
+     * Callback for creating the XMLHttpRequest object. Defaults to the ActiveXObject when available (IE), the XMLHttpRequest otherwise. Override to provide your own implementation for XMLHttpRequest or enhancements to the factory.
+     */
+    xhr?: any;
+    /**
+     * An object of fieldName-fieldValue pairs to set on the native XHR object. For example, you can use it to set withCredentials to true for cross-domain requests if needed. In jQuery 1.5, the withCredentials property was not propagated to the native XHR and thus CORS requests requiring it would ignore this flag. For this reason, we recommend using jQuery 1.5.1+ should you require the use of it. (version added: 1.5.1)
+     */
+    xhrFields?: { [key: string]: any; };
+}
+
+/**
+ * Interface for the jqXHR object
+ */
+interface JQueryXHR extends XMLHttpRequest, JQueryPromise<any> {
+    /**
+     * The .overrideMimeType() method may be used in the beforeSend() callback function, for example, to modify the response content-type header. As of jQuery 1.5.1, the jqXHR object also contains the overrideMimeType() method (it was available in jQuery 1.4.x, as well, but was temporarily removed in jQuery 1.5). 
+     */
+    overrideMimeType(mimeType: string): any;
+    /**
+     * Cancel the request. 
+     *
+     * @param statusText A string passed as the textStatus parameter for the done callback. Default value: "canceled"
+     */
+    abort(statusText?: string): void;
+    /**
+     * Incorporates the functionality of the .done() and .fail() methods, allowing (as of jQuery 1.8) the underlying Promise to be manipulated. Refer to deferred.then() for implementation details.
+     */
+    then(doneCallback: (data: any, textStatus: string, jqXHR: JQueryXHR) => void, failCallback?: (jqXHR: JQueryXHR, textStatus: string, errorThrown: any) => void): JQueryPromise<any>;
+    /**
+     * Property containing the parsed response if the response Content-Type is json
+     */
+    responseJSON?: any;
+    /**
+     * A function to be called if the request fails.
+     */
+    error(xhr: JQueryXHR, textStatus: string, errorThrown: string): void;
+}
+
+/**
+ * Interface for the JQuery callback
+ */
+interface JQueryCallback {
+    /**
+     * Add a callback or a collection of callbacks to a callback list.
+     * 
+     * @param callbacks A function, or array of functions, that are to be added to the callback list.
+     */
+    add(callbacks: Function): JQueryCallback;
+    /**
+     * Add a callback or a collection of callbacks to a callback list.
+     * 
+     * @param callbacks A function, or array of functions, that are to be added to the callback list.
+     */
+    add(callbacks: Function[]): JQueryCallback;
+
+    /**
+     * Disable a callback list from doing anything more.
+     */
+    disable(): JQueryCallback;
+
+    /**
+     * Determine if the callbacks list has been disabled.
+     */
+    disabled(): boolean;
+
+    /**
+     * Remove all of the callbacks from a list.
+     */
+    empty(): JQueryCallback;
+
+    /**
+     * Call all of the callbacks with the given arguments
+     * 
+     * @param arguments The argument or list of arguments to pass back to the callback list.
+     */
+    fire(...arguments: any[]): JQueryCallback;
+
+    /**
+     * Determine if the callbacks have already been called at least once.
+     */
+    fired(): boolean;
+
+    /**
+     * Call all callbacks in a list with the given context and arguments.
+     * 
+     * @param context A reference to the context in which the callbacks in the list should be fired.
+     * @param arguments An argument, or array of arguments, to pass to the callbacks in the list.
+     */
+    fireWith(context?: any, args?: any[]): JQueryCallback;
+
+    /**
+     * Determine whether a supplied callback is in a list
+     * 
+     * @param callback The callback to search for.
+     */
+    has(callback: Function): boolean;
+
+    /**
+     * Lock a callback list in its current state.
+     */
+    lock(): JQueryCallback;
+
+    /**
+     * Determine if the callbacks list has been locked.
+     */
+    locked(): boolean;
+
+    /**
+     * Remove a callback or a collection of callbacks from a callback list.
+     * 
+     * @param callbacks A function, or array of functions, that are to be removed from the callback list.
+     */
+    remove(callbacks: Function): JQueryCallback;
+    /**
+     * Remove a callback or a collection of callbacks from a callback list.
+     * 
+     * @param callbacks A function, or array of functions, that are to be removed from the callback list.
+     */
+    remove(callbacks: Function[]): JQueryCallback;
+}
+
+/**
+ * Allows jQuery Promises to interop with non-jQuery promises
+ */
+interface JQueryGenericPromise<T> {
+    /**
+     * Add handlers to be called when the Deferred object is resolved, rejected, or still in progress.
+     * 
+     * @param doneFilter A function that is called when the Deferred is resolved.
+     * @param failFilter An optional function that is called when the Deferred is rejected.
+     */
+    then<U>(doneFilter: (value?: T, ...values: any[]) => U|JQueryPromise<U>, failFilter?: (...reasons: any[]) => any, progressFilter?: (...progression: any[]) => any): JQueryPromise<U>;
+
+    /**
+     * Add handlers to be called when the Deferred object is resolved, rejected, or still in progress.
+     * 
+     * @param doneFilter A function that is called when the Deferred is resolved.
+     * @param failFilter An optional function that is called when the Deferred is rejected.
+     */
+    then(doneFilter: (value?: T, ...values: any[]) => void, failFilter?: (...reasons: any[]) => any, progressFilter?: (...progression: any[]) => any): JQueryPromise<void>;
+}
+
+/**
+ * Interface for the JQuery promise/deferred callbacks
+ */
+interface JQueryPromiseCallback<T> {
+    (value?: T, ...args: any[]): void;
+}
+
+interface JQueryPromiseOperator<T, U> {
+    (callback1: JQueryPromiseCallback<T>|JQueryPromiseCallback<T>[], ...callbacksN: Array<JQueryPromiseCallback<any>|JQueryPromiseCallback<any>[]>): JQueryPromise<U>;
+}
+
+/**
+ * Interface for the JQuery promise, part of callbacks
+ */
+interface JQueryPromise<T> extends JQueryGenericPromise<T> {
+    /**
+     * Determine the current state of a Deferred object.
+     */
+    state(): string;
+    /**
+     * Add handlers to be called when the Deferred object is either resolved or rejected.
+     * 
+     * @param alwaysCallbacks1 A function, or array of functions, that is called when the Deferred is resolved or rejected.
+     * @param alwaysCallbacks2 Optional additional functions, or arrays of functions, that are called when the Deferred is resolved or rejected.
+     */
+    always(alwaysCallback1?: JQueryPromiseCallback<any>|JQueryPromiseCallback<any>[], ...alwaysCallbacksN: Array<JQueryPromiseCallback<any>|JQueryPromiseCallback<any>[]>): JQueryPromise<T>;
+    /**
+     * Add handlers to be called when the Deferred object is resolved.
+     * 
+     * @param doneCallbacks1 A function, or array of functions, that are called when the Deferred is resolved.
+     * @param doneCallbacks2 Optional additional functions, or arrays of functions, that are called when the Deferred is resolved.
+     */
+    done(doneCallback1?: JQueryPromiseCallback<T>|JQueryPromiseCallback<T>[], ...doneCallbackN: Array<JQueryPromiseCallback<T>|JQueryPromiseCallback<T>[]>): JQueryPromise<T>;
+    /**
+     * Add handlers to be called when the Deferred object is rejected.
+     * 
+     * @param failCallbacks1 A function, or array of functions, that are called when the Deferred is rejected.
+     * @param failCallbacks2 Optional additional functions, or arrays of functions, that are called when the Deferred is rejected.
+     */
+    fail(failCallback1?: JQueryPromiseCallback<any>|JQueryPromiseCallback<any>[], ...failCallbacksN: Array<JQueryPromiseCallback<any>|JQueryPromiseCallback<any>[]>): JQueryPromise<T>;
+    /**
+     * Add handlers to be called when the Deferred object generates progress notifications.
+     * 
+     * @param progressCallbacks A function, or array of functions, to be called when the Deferred generates progress notifications.
+     */
+    progress(progressCallback1?: JQueryPromiseCallback<any>|JQueryPromiseCallback<any>[], ...progressCallbackN: Array<JQueryPromiseCallback<any>|JQueryPromiseCallback<any>[]>): JQueryPromise<T>;
+
+    // Deprecated - given no typings
+    pipe(doneFilter?: (x: any) => any, failFilter?: (x: any) => any, progressFilter?: (x: any) => any): JQueryPromise<any>;
+}
+
+/**
+ * Interface for the JQuery deferred, part of callbacks
+ */
+interface JQueryDeferred<T> extends JQueryGenericPromise<T> {
+    /**
+     * Determine the current state of a Deferred object.
+     */
+    state(): string;
+    /**
+     * Add handlers to be called when the Deferred object is either resolved or rejected.
+     * 
+     * @param alwaysCallbacks1 A function, or array of functions, that is called when the Deferred is resolved or rejected.
+     * @param alwaysCallbacks2 Optional additional functions, or arrays of functions, that are called when the Deferred is resolved or rejected.
+     */
+    always(alwaysCallback1?: JQueryPromiseCallback<any>|JQueryPromiseCallback<any>[], ...alwaysCallbacksN: Array<JQueryPromiseCallback<any>|JQueryPromiseCallback<any>[]>): JQueryDeferred<T>;
+    /**
+     * Add handlers to be called when the Deferred object is resolved.
+     * 
+     * @param doneCallbacks1 A function, or array of functions, that are called when the Deferred is resolved.
+     * @param doneCallbacks2 Optional additional functions, or arrays of functions, that are called when the Deferred is resolved.
+     */
+    done(doneCallback1?: JQueryPromiseCallback<T>|JQueryPromiseCallback<T>[], ...doneCallbackN: Array<JQueryPromiseCallback<T>|JQueryPromiseCallback<T>[]>): JQueryDeferred<T>;
+    /**
+     * Add handlers to be called when the Deferred object is rejected.
+     * 
+     * @param failCallbacks1 A function, or array of functions, that are called when the Deferred is rejected.
+     * @param failCallbacks2 Optional additional functions, or arrays of functions, that are called when the Deferred is rejected.
+     */
+    fail(failCallback1?: JQueryPromiseCallback<any>|JQueryPromiseCallback<any>[], ...failCallbacksN: Array<JQueryPromiseCallback<any>|JQueryPromiseCallback<any>[]>): JQueryDeferred<T>;
+    /**
+     * Add handlers to be called when the Deferred object generates progress notifications.
+     * 
+     * @param progressCallbacks A function, or array of functions, to be called when the Deferred generates progress notifications.
+     */
+    progress(progressCallback1?: JQueryPromiseCallback<any>|JQueryPromiseCallback<any>[], ...progressCallbackN: Array<JQueryPromiseCallback<any>|JQueryPromiseCallback<any>[]>): JQueryDeferred<T>;
+
+    /**
+     * Call the progressCallbacks on a Deferred object with the given args.
+     * 
+     * @param args Optional arguments that are passed to the progressCallbacks.
+     */
+    notify(value?: any, ...args: any[]): JQueryDeferred<T>;
+
+    /**
+     * Call the progressCallbacks on a Deferred object with the given context and args.
+     * 
+     * @param context Context passed to the progressCallbacks as the this object.
+     * @param args Optional arguments that are passed to the progressCallbacks.
+     */
+    notifyWith(context: any, value?: any[]): JQueryDeferred<T>;
+
+    /**
+     * Reject a Deferred object and call any failCallbacks with the given args.
+     * 
+     * @param args Optional arguments that are passed to the failCallbacks.
+     */
+    reject(value?: any, ...args: any[]): JQueryDeferred<T>;
+    /**
+     * Reject a Deferred object and call any failCallbacks with the given context and args.
+     * 
+     * @param context Context passed to the failCallbacks as the this object.
+     * @param args An optional array of arguments that are passed to the failCallbacks.
+     */
+    rejectWith(context: any, value?: any[]): JQueryDeferred<T>;
+
+    /**
+     * Resolve a Deferred object and call any doneCallbacks with the given args.
+     * 
+     * @param value First argument passed to doneCallbacks.
+     * @param args Optional subsequent arguments that are passed to the doneCallbacks.
+     */
+    resolve(value?: T, ...args: any[]): JQueryDeferred<T>;
+
+    /**
+     * Resolve a Deferred object and call any doneCallbacks with the given context and args.
+     * 
+     * @param context Context passed to the doneCallbacks as the this object.
+     * @param args An optional array of arguments that are passed to the doneCallbacks.
+     */
+    resolveWith(context: any, value?: T[]): JQueryDeferred<T>;
+
+    /**
+     * Return a Deferred's Promise object.
+     * 
+     * @param target Object onto which the promise methods have to be attached
+     */
+    promise(target?: any): JQueryPromise<T>;
+
+    // Deprecated - given no typings
+    pipe(doneFilter?: (x: any) => any, failFilter?: (x: any) => any, progressFilter?: (x: any) => any): JQueryPromise<any>;
+}
+
+/**
+ * Interface of the JQuery extension of the W3C event object
+ */
+interface BaseJQueryEventObject extends Event {
+    data: any;
+    delegateTarget: Element;
+    isDefaultPrevented(): boolean;
+    isImmediatePropagationStopped(): boolean;
+    isPropagationStopped(): boolean;
+    namespace: string;
+    originalEvent: Event;
+    preventDefault(): any;
+    relatedTarget: Element;
+    result: any;
+    stopImmediatePropagation(): void;
+    stopPropagation(): void;
+    target: Element;
+    pageX: number;
+    pageY: number;
+    which: number;
+    metaKey: boolean;
+}
+
+interface JQueryInputEventObject extends BaseJQueryEventObject {
+    altKey: boolean;
+    ctrlKey: boolean;
+    metaKey: boolean;
+    shiftKey: boolean;
+}
+
+interface JQueryMouseEventObject extends JQueryInputEventObject {
+    button: number;
+    clientX: number;
+    clientY: number;
+    offsetX: number;
+    offsetY: number;
+    pageX: number;
+    pageY: number;
+    screenX: number;
+    screenY: number;
+}
+
+interface JQueryKeyEventObject extends JQueryInputEventObject {
+    char: any;
+    charCode: number;
+    key: any;
+    keyCode: number;
+}
+
+interface JQueryEventObject extends BaseJQueryEventObject, JQueryInputEventObject, JQueryMouseEventObject, JQueryKeyEventObject{
+}
+
+/*
+    Collection of properties of the current browser
+*/
+
+interface JQuerySupport {
+    ajax?: boolean;
+    boxModel?: boolean;
+    changeBubbles?: boolean;
+    checkClone?: boolean;
+    checkOn?: boolean;
+    cors?: boolean;
+    cssFloat?: boolean;
+    hrefNormalized?: boolean;
+    htmlSerialize?: boolean;
+    leadingWhitespace?: boolean;
+    noCloneChecked?: boolean;
+    noCloneEvent?: boolean;
+    opacity?: boolean;
+    optDisabled?: boolean;
+    optSelected?: boolean;
+    scriptEval? (): boolean;
+    style?: boolean;
+    submitBubbles?: boolean;
+    tbody?: boolean;
+}
+
+interface JQueryParam {
+    /**
+     * Create a serialized representation of an array or object, suitable for use in a URL query string or Ajax request.
+     * 
+     * @param obj An array or object to serialize.
+     */
+    (obj: any): string;
+
+    /**
+     * Create a serialized representation of an array or object, suitable for use in a URL query string or Ajax request.
+     * 
+     * @param obj An array or object to serialize.
+     * @param traditional A Boolean indicating whether to perform a traditional "shallow" serialization.
+     */
+    (obj: any, traditional: boolean): string;
+}
+
+/**
+ * The interface used to construct jQuery events (with $.Event). It is
+ * defined separately instead of inline in JQueryStatic to allow
+ * overriding the construction function with specific strings
+ * returning specific event objects.
+ */
+interface JQueryEventConstructor {
+    (name: string, eventProperties?: any): JQueryEventObject;
+    new (name: string, eventProperties?: any): JQueryEventObject;
+}
+
+/**
+ * The interface used to specify coordinates.
+ */
+interface JQueryCoordinates {
+    left: number;
+    top: number;
+}
+
+/**
+ * Elements in the array returned by serializeArray()
+ */
+interface JQuerySerializeArrayElement {
+    name: string;
+    value: string;
+}
+
+interface JQueryAnimationOptions { 
+    /**
+     * A string or number determining how long the animation will run.
+     */
+    duration?: any; 
+    /**
+     * A string indicating which easing function to use for the transition.
+     */
+    easing?: string; 
+    /**
+     * A function to call once the animation is complete.
+     */
+    complete?: Function; 
+    /**
+     * A function to be called for each animated property of each animated element. This function provides an opportunity to modify the Tween object to change the value of the property before it is set.
+     */
+    step?: (now: number, tween: any) => any; 
+    /**
+     * A function to be called after each step of the animation, only once per animated element regardless of the number of animated properties. (version added: 1.8)
+     */
+    progress?: (animation: JQueryPromise<any>, progress: number, remainingMs: number) => any; 
+    /**
+     * A function to call when the animation begins. (version added: 1.8)
+     */
+    start?: (animation: JQueryPromise<any>) => any; 
+    /**
+     * A function to be called when the animation completes (its Promise object is resolved). (version added: 1.8)
+     */
+    done?: (animation: JQueryPromise<any>, jumpedToEnd: boolean) => any; 
+    /**
+     * A function to be called when the animation fails to complete (its Promise object is rejected). (version added: 1.8)
+     */
+    fail?: (animation: JQueryPromise<any>, jumpedToEnd: boolean) => any; 
+    /**
+     * A function to be called when the animation completes or stops without completing (its Promise object is either resolved or rejected). (version added: 1.8)
+     */
+    always?: (animation: JQueryPromise<any>, jumpedToEnd: boolean) => any; 
+    /**
+     * A Boolean indicating whether to place the animation in the effects queue. If false, the animation will begin immediately. As of jQuery 1.7, the queue option can also accept a string, in which case the animation is added to the queue represented by that string. When a custom queue name is used the animation does not automatically start; you must call .dequeue("queuename") to start it.
+     */
+    queue?: any; 
+    /**
+     * A map of one or more of the CSS properties defined by the properties argument and their corresponding easing functions. (version added: 1.4)
+     */
+    specialEasing?: Object;
+}
+
+interface JQueryEasingFunction {
+    ( percent: number ): number;
+}
+
+interface JQueryEasingFunctions {
+    [ name: string ]: JQueryEasingFunction;
+    linear: JQueryEasingFunction;
+    swing: JQueryEasingFunction;
+}
+
+/**
+ * Static members of jQuery (those on $ and jQuery themselves)
+ */
+interface JQueryStatic {
+
+    /**
+     * Perform an asynchronous HTTP (Ajax) request.
+     *
+     * @param settings A set of key/value pairs that configure the Ajax request. All settings are optional. A default can be set for any option with $.ajaxSetup().
+     */
+    ajax(settings: JQueryAjaxSettings): JQueryXHR;
+    /**
+     * Perform an asynchronous HTTP (Ajax) request.
+     *
+     * @param url A string containing the URL to which the request is sent.
+     * @param settings A set of key/value pairs that configure the Ajax request. All settings are optional. A default can be set for any option with $.ajaxSetup().
+     */
+    ajax(url: string, settings?: JQueryAjaxSettings): JQueryXHR;
+
+    /**
+     * Handle custom Ajax options or modify existing options before each request is sent and before they are processed by $.ajax().
+     *
+     * @param dataTypes An optional string containing one or more space-separated dataTypes
+     * @param handler A handler to set default values for future Ajax requests.
+     */
+    ajaxPrefilter(dataTypes: string, handler: (opts: any, originalOpts: JQueryAjaxSettings, jqXHR: JQueryXHR) => any): void;
+    /**
+     * Handle custom Ajax options or modify existing options before each request is sent and before they are processed by $.ajax().
+     *
+     * @param handler A handler to set default values for future Ajax requests.
+     */
+    ajaxPrefilter(handler: (opts: any, originalOpts: JQueryAjaxSettings, jqXHR: JQueryXHR) => any): void;
+
+    ajaxSettings: JQueryAjaxSettings;
+
+     /**
+      * Set default values for future Ajax requests. Its use is not recommended.
+      *
+      * @param options A set of key/value pairs that configure the default Ajax request. All options are optional.
+      */
+    ajaxSetup(options: JQueryAjaxSettings): void;
+
+    /**
+     * Load data from the server using a HTTP GET request.
+     *
+     * @param url A string containing the URL to which the request is sent.
+     * @param success A callback function that is executed if the request succeeds.
+     * @param dataType The type of data expected from the server. Default: Intelligent Guess (xml, json, script, or html).
+     */
+    get(url: string, success?: (data: any, textStatus: string, jqXHR: JQueryXHR) => any, dataType?: string): JQueryXHR;
+    /**
+     * Load data from the server using a HTTP GET request.
+     *
+     * @param url A string containing the URL to which the request is sent.
+     * @param data A plain object or string that is sent to the server with the request.
+     * @param success A callback function that is executed if the request succeeds.
+     * @param dataType The type of data expected from the server. Default: Intelligent Guess (xml, json, script, or html).
+     */
+    get(url: string, data?: Object|string, success?: (data: any, textStatus: string, jqXHR: JQueryXHR) => any, dataType?: string): JQueryXHR;
+    /**
+     * Load JSON-encoded data from the server using a GET HTTP request.
+     *
+     * @param url A string containing the URL to which the request is sent.
+     * @param success A callback function that is executed if the request succeeds.
+     */
+    getJSON(url: string, success?: (data: any, textStatus: string, jqXHR: JQueryXHR) => any): JQueryXHR;
+    /**
+     * Load JSON-encoded data from the server using a GET HTTP request.
+     *
+     * @param url A string containing the URL to which the request is sent.
+     * @param data A plain object or string that is sent to the server with the request.
+     * @param success A callback function that is executed if the request succeeds.
+     */
+    getJSON(url: string, data?: Object|string, success?: (data: any, textStatus: string, jqXHR: JQueryXHR) => any): JQueryXHR;
+    /**
+     * Load a JavaScript file from the server using a GET HTTP request, then execute it.
+     *
+     * @param url A string containing the URL to which the request is sent.
+     * @param success A callback function that is executed if the request succeeds.
+     */
+    getScript(url: string, success?: (script: string, textStatus: string, jqXHR: JQueryXHR) => any): JQueryXHR;
+
+    /**
+     * Create a serialized representation of an array or object, suitable for use in a URL query string or Ajax request.
+     */
+    param: JQueryParam;
+
+    /**
+     * Load data from the server using a HTTP POST request.
+     *
+     * @param url A string containing the URL to which the request is sent.
+     * @param success A callback function that is executed if the request succeeds. Required if dataType is provided, but can be null in that case.
+     * @param dataType The type of data expected from the server. Default: Intelligent Guess (xml, json, script, text, html).
+     */
+    post(url: string, success?: (data: any, textStatus: string, jqXHR: JQueryXHR) => any, dataType?: string): JQueryXHR;
+    /**
+     * Load data from the server using a HTTP POST request.
+     *
+     * @param url A string containing the URL to which the request is sent.
+     * @param data A plain object or string that is sent to the server with the request.
+     * @param success A callback function that is executed if the request succeeds. Required if dataType is provided, but can be null in that case.
+     * @param dataType The type of data expected from the server. Default: Intelligent Guess (xml, json, script, text, html).
+     */
+    post(url: string, data?: Object|string, success?: (data: any, textStatus: string, jqXHR: JQueryXHR) => any, dataType?: string): JQueryXHR;
+
+    /**
+     * A multi-purpose callbacks list object that provides a powerful way to manage callback lists.
+     *
+     * @param flags An optional list of space-separated flags that change how the callback list behaves.
+     */
+    Callbacks(flags?: string): JQueryCallback;
+
+    /**
+     * Holds or releases the execution of jQuery's ready event.
+     *
+     * @param hold Indicates whether the ready hold is being requested or released
+     */
+    holdReady(hold: boolean): void;
+
+    /**
+     * Accepts a string containing a CSS selector which is then used to match a set of elements.
+     *
+     * @param selector A string containing a selector expression
+     * @param context A DOM Element, Document, or jQuery to use as context
+     */
+    (selector: string, context?: Element|JQuery): JQuery;
+
+    /**
+     * Accepts a string containing a CSS selector which is then used to match a set of elements.
+     *
+     * @param element A DOM element to wrap in a jQuery object.
+     */
+    (element: Element): JQuery;
+
+    /**
+     * Accepts a string containing a CSS selector which is then used to match a set of elements.
+     *
+     * @param elementArray An array containing a set of DOM elements to wrap in a jQuery object.
+     */
+    (elementArray: Element[]): JQuery;
+
+    /**
+     * Binds a function to be executed when the DOM has finished loading.
+     *
+     * @param callback A function to execute after the DOM is ready.
+     */
+    (callback: (jQueryAlias?: JQueryStatic) => any): JQuery;
+
+    /**
+     * Accepts a string containing a CSS selector which is then used to match a set of elements.
+     *
+     * @param object A plain object to wrap in a jQuery object.
+     */
+    (object: {}): JQuery;
+
+    /**
+     * Accepts a string containing a CSS selector which is then used to match a set of elements.
+     *
+     * @param object An existing jQuery object to clone.
+     */
+    (object: JQuery): JQuery;
+
+    /**
+     * Specify a function to execute when the DOM is fully loaded.
+     */
+    (): JQuery;
+
+    /**
+     * Creates DOM elements on the fly from the provided string of raw HTML.
+     *
+     * @param html A string of HTML to create on the fly. Note that this parses HTML, not XML.
+     * @param ownerDocument A document in which the new elements will be created.
+     */
+    (html: string, ownerDocument?: Document): JQuery;
+
+    /**
+     * Creates DOM elements on the fly from the provided string of raw HTML.
+     *
+     * @param html A string defining a single, standalone, HTML element (e.g. <div/> or <div></div>).
+     * @param attributes An object of attributes, events, and methods to call on the newly-created element.
+     */
+    (html: string, attributes: Object): JQuery;
+
+    /**
+     * Relinquish jQuery's control of the $ variable.
+     *
+     * @param removeAll A Boolean indicating whether to remove all jQuery variables from the global scope (including jQuery itself).
+     */
+    noConflict(removeAll?: boolean): Object;
+
+    /**
+     * Provides a way to execute callback functions based on one or more objects, usually Deferred objects that represent asynchronous events.
+     *
+     * @param deferreds One or more Deferred objects, or plain JavaScript objects.
+     */
+    when<T>(...deferreds: Array<T|JQueryPromise<T>/* as JQueryDeferred<T> */>): JQueryPromise<T>;
+
+    /**
+     * Hook directly into jQuery to override how particular CSS properties are retrieved or set, normalize CSS property naming, or create custom properties.
+     */
+    cssHooks: { [key: string]: any; };
+    cssNumber: any;
+
+    /**
+     * Store arbitrary data associated with the specified element. Returns the value that was set.
+     *
+     * @param element The DOM element to associate with the data.
+     * @param key A string naming the piece of data to set.
+     * @param value The new data value.
+     */
+    data<T>(element: Element, key: string, value: T): T;
+    /**
+     * Returns value at named data store for the element, as set by jQuery.data(element, name, value), or the full data store for the element.
+     *
+     * @param element The DOM element to associate with the data.
+     * @param key A string naming the piece of data to set.
+     */
+    data(element: Element, key: string): any;
+    /**
+     * Returns value at named data store for the element, as set by jQuery.data(element, name, value), or the full data store for the element.
+     *
+     * @param element The DOM element to associate with the data.
+     */
+    data(element: Element): any;
+
+    /**
+     * Execute the next function on the queue for the matched element.
+     *
+     * @param element A DOM element from which to remove and execute a queued function.
+     * @param queueName A string containing the name of the queue. Defaults to fx, the standard effects queue.
+     */
+    dequeue(element: Element, queueName?: string): void;
+
+    /**
+     * Determine whether an element has any jQuery data associated with it.
+     *
+     * @param element A DOM element to be checked for data.
+     */
+    hasData(element: Element): boolean;
+
+    /**
+     * Show the queue of functions to be executed on the matched element.
+     *
+     * @param element A DOM element to inspect for an attached queue.
+     * @param queueName A string containing the name of the queue. Defaults to fx, the standard effects queue.
+     */
+    queue(element: Element, queueName?: string): any[];
+    /**
+     * Manipulate the queue of functions to be executed on the matched element.
+     *
+     * @param element A DOM element where the array of queued functions is attached.
+     * @param queueName A string containing the name of the queue. Defaults to fx, the standard effects queue.
+     * @param newQueue An array of functions to replace the current queue contents.
+     */
+    queue(element: Element, queueName: string, newQueue: Function[]): JQuery;
+    /**
+     * Manipulate the queue of functions to be executed on the matched element.
+     *
+     * @param element A DOM element on which to add a queued function.
+     * @param queueName A string containing the name of the queue. Defaults to fx, the standard effects queue.
+     * @param callback The new function to add to the queue.
+     */
+    queue(element: Element, queueName: string, callback: Function): JQuery;
+
+    /**
+     * Remove a previously-stored piece of data.
+     *
+     * @param element A DOM element from which to remove data.
+     * @param name A string naming the piece of data to remove.
+     */
+    removeData(element: Element, name?: string): JQuery;
+
+    /**
+     * A constructor function that returns a chainable utility object with methods to register multiple callbacks into callback queues, invoke callback queues, and relay the success or failure state of any synchronous or asynchronous function.
+     *
+     * @param beforeStart A function that is called just before the constructor returns.
+     */
+    Deferred<T>(beforeStart?: (deferred: JQueryDeferred<T>) => any): JQueryDeferred<T>;
+
+    /**
+     * Effects
+     */
+
+    easing: JQueryEasingFunctions;
+
+    fx: {
+        tick: () => void;
+        /**
+         * The rate (in milliseconds) at which animations fire.
+         */
+        interval: number;
+        stop: () => void;
+        speeds: { slow: number; fast: number; };
+        /**
+         * Globally disable all animations.
+         */
+        off: boolean;
+        step: any;
+    };
+
+    /**
+     * Takes a function and returns a new one that will always have a particular context.
+     *
+     * @param fnction The function whose context will be changed.
+     * @param context The object to which the context (this) of the function should be set.
+     * @param additionalArguments Any number of arguments to be passed to the function referenced in the function argument.
+     */
+    proxy(fnction: (...args: any[]) => any, context: Object, ...additionalArguments: any[]): any;
+    /**
+     * Takes a function and returns a new one that will always have a particular context.
+     *
+     * @param context The object to which the context (this) of the function should be set.
+     * @param name The name of the function whose context will be changed (should be a property of the context object).
+     * @param additionalArguments Any number of arguments to be passed to the function named in the name argument.
+     */
+    proxy(context: Object, name: string, ...additionalArguments: any[]): any;
+
+    Event: JQueryEventConstructor;
+
+    /**
+     * Takes a string and throws an exception containing it.
+     *
+     * @param message The message to send out.
+     */
+    error(message: any): JQuery;
+
+    expr: any;
+    fn: any;  //TODO: Decide how we want to type this
+
+    isReady: boolean;
+
+    // Properties
+    support: JQuerySupport;
+
+    /**
+     * Check to see if a DOM element is a descendant of another DOM element.
+     * 
+     * @param container The DOM element that may contain the other element.
+     * @param contained The DOM element that may be contained by (a descendant of) the other element.
+     */
+    contains(container: Element, contained: Element): boolean;
+
+    /**
+     * A generic iterator function, which can be used to seamlessly iterate over both objects and arrays. Arrays and array-like objects with a length property (such as a function's arguments object) are iterated by numeric index, from 0 to length-1. Other objects are iterated via their named properties.
+     * 
+     * @param collection The object or array to iterate over.
+     * @param callback The function that will be executed on every object.
+     */
+    each<T>(
+        collection: T[],
+        callback: (indexInArray: number, valueOfElement: T) => any
+        ): any;
+
+    /**
+     * A generic iterator function, which can be used to seamlessly iterate over both objects and arrays. Arrays and array-like objects with a length property (such as a function's arguments object) are iterated by numeric index, from 0 to length-1. Other objects are iterated via their named properties.
+     * 
+     * @param collection The object or array to iterate over.
+     * @param callback The function that will be executed on every object.
+     */
+    each(
+        collection: any,
+        callback: (indexInArray: any, valueOfElement: any) => any
+        ): any;
+
+    /**
+     * Merge the contents of two or more objects together into the first object.
+     *
+     * @param target An object that will receive the new properties if additional objects are passed in or that will extend the jQuery namespace if it is the sole argument.
+     * @param object1 An object containing additional properties to merge in.
+     * @param objectN Additional objects containing properties to merge in.
+     */
+    extend(target: any, object1?: any, ...objectN: any[]): any;
+    /**
+     * Merge the contents of two or more objects together into the first object.
+     *
+     * @param deep If true, the merge becomes recursive (aka. deep copy).
+     * @param target The object to extend. It will receive the new properties.
+     * @param object1 An object containing additional properties to merge in.
+     * @param objectN Additional objects containing properties to merge in.
+     */
+    extend(deep: boolean, target: any, object1?: any, ...objectN: any[]): any;
+
+    /**
+     * Execute some JavaScript code globally.
+     *
+     * @param code The JavaScript code to execute.
+     */
+    globalEval(code: string): any;
+
+    /**
+     * Finds the elements of an array which satisfy a filter function. The original array is not affected.
+     *
+     * @param array The array to search through.
+     * @param func The function to process each item against. The first argument to the function is the item, and the second argument is the index. The function should return a Boolean value.  this will be the global window object.
+     * @param invert If "invert" is false, or not provided, then the function returns an array consisting of all elements for which "callback" returns true. If "invert" is true, then the function returns an array consisting of all elements for which "callback" returns false.
+     */
+    grep<T>(array: T[], func: (elementOfArray: T, indexInArray: number) => boolean, invert?: boolean): T[];
+
+    /**
+     * Search for a specified value within an array and return its index (or -1 if not found).
+     *
+     * @param value The value to search for.
+     * @param array An array through which to search.
+     * @param fromIndex he index of the array at which to begin the search. The default is 0, which will search the whole array.
+     */
+    inArray<T>(value: T, array: T[], fromIndex?: number): number;
+
+    /**
+     * Determine whether the argument is an array.
+     *
+     * @param obj Object to test whether or not it is an array.
+     */
+    isArray(obj: any): boolean;
+    /**
+     * Check to see if an object is empty (contains no enumerable properties).
+     *
+     * @param obj The object that will be checked to see if it's empty.
+     */
+    isEmptyObject(obj: any): boolean;
+    /**
+     * Determine if the argument passed is a Javascript function object.
+     *
+     * @param obj Object to test whether or not it is a function.
+     */
+    isFunction(obj: any): boolean;
+    /**
+     * Determines whether its argument is a number.
+     *
+     * @param obj The value to be tested.
+     */
+    isNumeric(value: any): boolean;
+    /**
+     * Check to see if an object is a plain object (created using "{}" or "new Object").
+     *
+     * @param obj The object that will be checked to see if it's a plain object.
+     */
+    isPlainObject(obj: any): boolean;
+    /**
+     * Determine whether the argument is a window.
+     *
+     * @param obj Object to test whether or not it is a window.
+     */
+    isWindow(obj: any): boolean;
+    /**
+     * Check to see if a DOM node is within an XML document (or is an XML document).
+     *
+     * @param node he DOM node that will be checked to see if it's in an XML document.
+     */
+    isXMLDoc(node: Node): boolean;
+
+    /**
+     * Convert an array-like object into a true JavaScript array.
+     * 
+     * @param obj Any object to turn into a native Array.
+     */
+    makeArray(obj: any): any[];
+
+    /**
+     * Translate all items in an array or object to new array of items.
+     * 
+     * @param array The Array to translate.
+     * @param callback The function to process each item against. The first argument to the function is the array item, the second argument is the index in array The function can return any value. Within the function, this refers to the global (window) object.
+     */
+    map<T, U>(array: T[], callback: (elementOfArray: T, indexInArray: number) => U): U[];
+    /**
+     * Translate all items in an array or object to new array of items.
+     * 
+     * @param arrayOrObject The Array or Object to translate.
+     * @param callback The function to process each item against. The first argument to the function is the value; the second argument is the index or key of the array or object property. The function can return any value to add to the array. A returned array will be flattened into the resulting array. Within the function, this refers to the global (window) object.
+     */
+    map(arrayOrObject: any, callback: (value: any, indexOrKey: any) => any): any;
+
+    /**
+     * Merge the contents of two arrays together into the first array.
+     * 
+     * @param first The first array to merge, the elements of second added.
+     * @param second The second array to merge into the first, unaltered.
+     */
+    merge<T>(first: T[], second: T[]): T[];
+
+    /**
+     * An empty function.
+     */
+    noop(): any;
+
+    /**
+     * Return a number representing the current time.
+     */
+    now(): number;
+
+    /**
+     * Takes a well-formed JSON string and returns the resulting JavaScript object.
+     * 
+     * @param json The JSON string to parse.
+     */
+    parseJSON(json: string): any;
+
+    /**
+     * Parses a string into an XML document.
+     *
+     * @param data a well-formed XML string to be parsed
+     */
+    parseXML(data: string): XMLDocument;
+
+    /**
+     * Remove the whitespace from the beginning and end of a string.
+     * 
+     * @param str Remove the whitespace from the beginning and end of a string.
+     */
+    trim(str: string): string;
+
+    /**
+     * Determine the internal JavaScript [[Class]] of an object.
+     * 
+     * @param obj Object to get the internal JavaScript [[Class]] of.
+     */
+    type(obj: any): string;
+
+    /**
+     * Sorts an array of DOM elements, in place, with the duplicates removed. Note that this only works on arrays of DOM elements, not strings or numbers.
+     * 
+     * @param array The Array of DOM elements.
+     */
+    unique(array: Element[]): Element[];
+
+    /**
+     * Parses a string into an array of DOM nodes.
+     *
+     * @param data HTML string to be parsed
+     * @param context DOM element to serve as the context in which the HTML fragment will be created
+     * @param keepScripts A Boolean indicating whether to include scripts passed in the HTML string
+     */
+    parseHTML(data: string, context?: HTMLElement, keepScripts?: boolean): any[];
+
+    /**
+     * Parses a string into an array of DOM nodes.
+     *
+     * @param data HTML string to be parsed
+     * @param context DOM element to serve as the context in which the HTML fragment will be created
+     * @param keepScripts A Boolean indicating whether to include scripts passed in the HTML string
+     */
+    parseHTML(data: string, context?: Document, keepScripts?: boolean): any[];
+}
+
+/**
+ * The jQuery instance members
+ */
+interface JQuery {
+    /**
+     * Register a handler to be called when Ajax requests complete. This is an AjaxEvent.
+     *
+     * @param handler The function to be invoked.
+     */
+    ajaxComplete(handler: (event: JQueryEventObject, XMLHttpRequest: XMLHttpRequest, ajaxOptions: any) => any): JQuery;
+    /**
+     * Register a handler to be called when Ajax requests complete with an error. This is an Ajax Event.
+     *
+     * @param handler The function to be invoked.
+     */
+    ajaxError(handler: (event: JQueryEventObject, jqXHR: JQueryXHR, ajaxSettings: JQueryAjaxSettings, thrownError: any) => any): JQuery;
+    /**
+     * Attach a function to be executed before an Ajax request is sent. This is an Ajax Event.
+     *
+     * @param handler The function to be invoked.
+     */
+    ajaxSend(handler: (event: JQueryEventObject, jqXHR: JQueryXHR, ajaxOptions: JQueryAjaxSettings) => any): JQuery;
+    /**
+     * Register a handler to be called when the first Ajax request begins. This is an Ajax Event.
+     *
+     * @param handler The function to be invoked.
+     */
+    ajaxStart(handler: () => any): JQuery;
+    /**
+     * Register a handler to be called when all Ajax requests have completed. This is an Ajax Event.
+     *
+     * @param handler The function to be invoked.
+     */
+    ajaxStop(handler: () => any): JQuery;
+    /**
+     * Attach a function to be executed whenever an Ajax request completes successfully. This is an Ajax Event.
+     *
+     * @param handler The function to be invoked.
+     */
+    ajaxSuccess(handler: (event: JQueryEventObject, XMLHttpRequest: XMLHttpRequest, ajaxOptions: JQueryAjaxSettings) => any): JQuery;
+
+    /**
+     * Load data from the server and place the returned HTML into the matched element.
+     *
+     * @param url A string containing the URL to which the request is sent.
+     * @param data A plain object or string that is sent to the server with the request.
+     * @param complete A callback function that is executed when the request completes.
+     */
+    load(url: string, data?: string|Object, complete?: (responseText: string, textStatus: string, XMLHttpRequest: XMLHttpRequest) => any): JQuery;
+
+    /**
+     * Encode a set of form elements as a string for submission.
+     */
+    serialize(): string;
+    /**
+     * Encode a set of form elements as an array of names and values.
+     */
+    serializeArray(): JQuerySerializeArrayElement[];
+
+    /**
+     * Adds the specified class(es) to each of the set of matched elements.
+     *
+     * @param className One or more space-separated classes to be added to the class attribute of each matched element.
+     */
+    addClass(className: string): JQuery;
+    /**
+     * Adds the specified class(es) to each of the set of matched elements.
+     *
+     * @param function A function returning one or more space-separated class names to be added to the existing class name(s). Receives the index position of the element in the set and the existing class name(s) as arguments. Within the function, this refers to the current element in the set.
+     */
+    addClass(func: (index: number, className: string) => string): JQuery;
+
+    /**
+     * Add the previous set of elements on the stack to the current set, optionally filtered by a selector.
+     */
+    addBack(selector?: string): JQuery;
+
+    /**
+     * Get the value of an attribute for the first element in the set of matched elements.
+     *
+     * @param attributeName The name of the attribute to get.
+     */
+    attr(attributeName: string): string;
+    /**
+     * Set one or more attributes for the set of matched elements.
+     *
+     * @param attributeName The name of the attribute to set.
+     * @param value A value to set for the attribute.
+     */
+    attr(attributeName: string, value: string|number): JQuery;
+    /**
+     * Set one or more attributes for the set of matched elements.
+     *
+     * @param attributeName The name of the attribute to set.
+     * @param func A function returning the value to set. this is the current element. Receives the index position of the element in the set and the old attribute value as arguments.
+     */
+    attr(attributeName: string, func: (index: number, attr: string) => string|number): JQuery;
+    /**
+     * Set one or more attributes for the set of matched elements.
+     *
+     * @param attributes An object of attribute-value pairs to set.
+     */
+    attr(attributes: Object): JQuery;
+    
+    /**
+     * Determine whether any of the matched elements are assigned the given class.
+     *
+     * @param className The class name to search for.
+     */
+    hasClass(className: string): boolean;
+
+    /**
+     * Get the HTML contents of the first element in the set of matched elements.
+     */
+    html(): string;
+    /**
+     * Set the HTML contents of each element in the set of matched elements.
+     *
+     * @param htmlString A string of HTML to set as the content of each matched element.
+     */
+    html(htmlString: string): JQuery;
+    /**
+     * Set the HTML contents of each element in the set of matched elements.
+     *
+     * @param func A function returning the HTML content to set. Receives the index position of the element in the set and the old HTML value as arguments. jQuery empties the element before calling the function; use the oldhtml argument to reference the previous content. Within the function, this refers to the current element in the set.
+     */
+    html(func: (index: number, oldhtml: string) => string): JQuery;
+    /**
+     * Set the HTML contents of each element in the set of matched elements.
+     *
+     * @param func A function returning the HTML content to set. Receives the index position of the element in the set and the old HTML value as arguments. jQuery empties the element before calling the function; use the oldhtml argument to reference the previous content. Within the function, this refers to the current element in the set.
+     */
+
+    /**
+     * Get the value of a property for the first element in the set of matched elements.
+     *
+     * @param propertyName The name of the property to get.
+     */
+    prop(propertyName: string): any;
+    /**
+     * Set one or more properties for the set of matched elements.
+     *
+     * @param propertyName The name of the property to set.
+     * @param value A value to set for the property.
+     */
+    prop(propertyName: string, value: string|number|boolean): JQuery;
+    /**
+     * Set one or more properties for the set of matched elements.
+     *
+     * @param properties An object of property-value pairs to set.
+     */
+    prop(properties: Object): JQuery;
+    /**
+     * Set one or more properties for the set of matched elements.
+     *
+     * @param propertyName The name of the property to set.
+     * @param func A function returning the value to set. Receives the index position of the element in the set and the old property value as arguments. Within the function, the keyword this refers to the current element.
+     */
+    prop(propertyName: string, func: (index: number, oldPropertyValue: any) => any): JQuery;
+
+    /**
+     * Remove an attribute from each element in the set of matched elements.
+     *
+     * @param attributeName An attribute to remove; as of version 1.7, it can be a space-separated list of attributes.
+     */
+    removeAttr(attributeName: string): JQuery;
+
+    /**
+     * Remove a single class, multiple classes, or all classes from each element in the set of matched elements.
+     *
+     * @param className One or more space-separated classes to be removed from the class attribute of each matched element.
+     */
+    removeClass(className?: string): JQuery;
+    /**
+     * Remove a single class, multiple classes, or all classes from each element in the set of matched elements.
+     *
+     * @param function A function returning one or more space-separated class names to be removed. Receives the index position of the element in the set and the old class value as arguments.
+     */
+    removeClass(func: (index: number, className: string) => string): JQuery;
+
+    /**
+     * Remove a property for the set of matched elements.
+     *
+     * @param propertyName The name of the property to remove.
+     */
+    removeProp(propertyName: string): JQuery;
+
+    /**
+     * Add or remove one or more classes from each element in the set of matched elements, depending on either the class's presence or the value of the switch argument.
+     *
+     * @param className One or more class names (separated by spaces) to be toggled for each element in the matched set.
+     * @param swtch A Boolean (not just truthy/falsy) value to determine whether the class should be added or removed.
+     */
+    toggleClass(className: string, swtch?: boolean): JQuery;
+    /**
+     * Add or remove one or more classes from each element in the set of matched elements, depending on either the class's presence or the value of the switch argument.
+     *
+     * @param swtch A boolean value to determine whether the class should be added or removed.
+     */
+    toggleClass(swtch?: boolean): JQuery;
+    /**
+     * Add or remove one or more classes from each element in the set of matched elements, depending on either the class's presence or the value of the switch argument.
+     *
+     * @param func A function that returns class names to be toggled in the class attribute of each element in the matched set. Receives the index position of the element in the set, the old class value, and the switch as arguments.
+     * @param swtch A boolean value to determine whether the class should be added or removed.
+     */
+    toggleClass(func: (index: number, className: string, swtch: boolean) => string, swtch?: boolean): JQuery;
+
+    /**
+     * Get the current value of the first element in the set of matched elements.
+     */
+    val(): any;
+    /**
+     * Set the value of each element in the set of matched elements.
+     *
+     * @param value A string of text, an array of strings or number corresponding to the value of each matched element to set as selected/checked.
+     */
+    val(value: string|string[]|number): JQuery;
+    /**
+     * Set the value of each element in the set of matched elements.
+     *
+     * @param func A function returning the value to set. this is the current element. Receives the index position of the element in the set and the old value as arguments.
+     */
+    val(func: (index: number, value: string) => string): JQuery;
+
+
+    /**
+     * Get the value of style properties for the first element in the set of matched elements.
+     *
+     * @param propertyName A CSS property.
+     */
+    css(propertyName: string): string;
+    /**
+     * Set one or more CSS properties for the set of matched elements.
+     *
+     * @param propertyName A CSS property name.
+     * @param value A value to set for the property.
+     */
+    css(propertyName: string, value: string|number): JQuery;
+    /**
+     * Set one or more CSS properties for the set of matched elements.
+     *
+     * @param propertyName A CSS property name.
+     * @param value A function returning the value to set. this is the current element. Receives the index position of the element in the set and the old value as arguments.
+     */
+    css(propertyName: string, value: (index: number, value: string) => string|number): JQuery;
+    /**
+     * Set one or more CSS properties for the set of matched elements.
+     *
+     * @param properties An object of property-value pairs to set.
+     */
+    css(properties: Object): JQuery;
+
+    /**
+     * Get the current computed height for the first element in the set of matched elements.
+     */
+    height(): number;
+    /**
+     * Set the CSS height of every matched element.
+     *
+     * @param value An integer representing the number of pixels, or an integer with an optional unit of measure appended (as a string).
+     */
+    height(value: number|string): JQuery;
+    /**
+     * Set the CSS height of every matched element.
+     *
+     * @param func A function returning the height to set. Receives the index position of the element in the set and the old height as arguments. Within the function, this refers to the current element in the set.
+     */
+    height(func: (index: number, height: number) => number|string): JQuery;
+
+    /**
+     * Get the current computed height for the first element in the set of matched elements, including padding but not border.
+     */
+    innerHeight(): number;
+
+    /**
+     * Sets the inner height on elements in the set of matched elements, including padding but not border.
+     *
+     * @param value An integer representing the number of pixels, or an integer along with an optional unit of measure appended (as a string).
+     */
+    innerHeight(height: number|string): JQuery;
+    
+    /**
+     * Get the current computed width for the first element in the set of matched elements, including padding but not border.
+     */
+    innerWidth(): number;
+
+    /**
+     * Sets the inner width on elements in the set of matched elements, including padding but not border.
+     *
+     * @param value An integer representing the number of pixels, or an integer along with an optional unit of measure appended (as a string).
+     */
+    innerWidth(width: number|string): JQuery;
+    
+    /**
+     * Get the current coordinates of the first element in the set of matched elements, relative to the document.
+     */
+    offset(): JQueryCoordinates;
+    /**
+     * An object containing the properties top and left, which are integers indicating the new top and left coordinates for the elements.
+     *
+     * @param coordinates An object containing the properties top and left, which are integers indicating the new top and left coordinates for the elements.
+     */
+    offset(coordinates: JQueryCoordinates): JQuery;
+    /**
+     * An object containing the properties top and left, which are integers indicating the new top and left coordinates for the elements.
+     *
+     * @param func A function to return the coordinates to set. Receives the index of the element in the collection as the first argument and the current coordinates as the second argument. The function should return an object with the new top and left properties.
+     */
+    offset(func: (index: number, coords: JQueryCoordinates) => JQueryCoordinates): JQuery;
+
+    /**
+     * Get the current computed height for the first element in the set of matched elements, including padding, border, and optionally margin. Returns an integer (without "px") representation of the value or null if called on an empty set of elements.
+     *
+     * @param includeMargin A Boolean indicating whether to include the element's margin in the calculation.
+     */
+    outerHeight(includeMargin?: boolean): number;
+
+    /**
+     * Sets the outer height on elements in the set of matched elements, including padding and border.
+     *
+     * @param value An integer representing the number of pixels, or an integer along with an optional unit of measure appended (as a string).
+     */
+    outerHeight(height: number|string): JQuery;
+
+    /**
+     * Get the current computed width for the first element in the set of matched elements, including padding and border.
+     *
+     * @param includeMargin A Boolean indicating whether to include the element's margin in the calculation.
+     */
+    outerWidth(includeMargin?: boolean): number;
+
+    /**
+     * Sets the outer width on elements in the set of matched elements, including padding and border.
+     *
+     * @param value An integer representing the number of pixels, or an integer along with an optional unit of measure appended (as a string).
+     */
+    outerWidth(width: number|string): JQuery;
+
+    /**
+     * Get the current coordinates of the first element in the set of matched elements, relative to the offset parent.
+     */
+    position(): JQueryCoordinates;
+
+    /**
+     * Get the current horizontal position of the scroll bar for the first element in the set of matched elements or set the horizontal position of the scroll bar for every matched element.
+     */
+    scrollLeft(): number;
+    /**
+     * Set the current horizontal position of the scroll bar for each of the set of matched elements.
+     *
+     * @param value An integer indicating the new position to set the scroll bar to.
+     */
+    scrollLeft(value: number): JQuery;
+
+    /**
+     * Get the current vertical position of the scroll bar for the first element in the set of matched elements or set the vertical position of the scroll bar for every matched element.
+     */
+    scrollTop(): number;
+    /**
+     * Set the current vertical position of the scroll bar for each of the set of matched elements.
+     *
+     * @param value An integer indicating the new position to set the scroll bar to.
+     */
+    scrollTop(value: number): JQuery;
+
+    /**
+     * Get the current computed width for the first element in the set of matched elements.
+     */
+    width(): number;
+    /**
+     * Set the CSS width of each element in the set of matched elements.
+     *
+     * @param value An integer representing the number of pixels, or an integer along with an optional unit of measure appended (as a string).
+     */
+    width(value: number|string): JQuery;
+    /**
+     * Set the CSS width of each element in the set of matched elements.
+     *
+     * @param func A function returning the width to set. Receives the index position of the element in the set and the old width as arguments. Within the function, this refers to the current element in the set.
+     */
+    width(func: (index: number, width: number) => number|string): JQuery;
+
+    /**
+     * Remove from the queue all items that have not yet been run.
+     *
+     * @param queueName A string containing the name of the queue. Defaults to fx, the standard effects queue.
+     */
+    clearQueue(queueName?: string): JQuery;
+
+    /**
+     * Store arbitrary data associated with the matched elements.
+     *
+     * @param key A string naming the piece of data to set.
+     * @param value The new data value; it can be any Javascript type including Array or Object.
+     */
+    data(key: string, value: any): JQuery;
+    /**
+     * Return the value at the named data store for the first element in the jQuery collection, as set by data(name, value) or by an HTML5 data-* attribute.
+     *
+     * @param key Name of the data stored.
+     */
+    data(key: string): any;
+    /**
+     * Store arbitrary data associated with the matched elements.
+     *
+     * @param obj An object of key-value pairs of data to update.
+     */
+    data(obj: { [key: string]: any; }): JQuery;
+    /**
+     * Return the value at the named data store for the first element in the jQuery collection, as set by data(name, value) or by an HTML5 data-* attribute.
+     */
+    data(): any;
+
+    /**
+     * Execute the next function on the queue for the matched elements.
+     *
+     * @param queueName A string containing the name of the queue. Defaults to fx, the standard effects queue.
+     */
+    dequeue(queueName?: string): JQuery;
+
+    /**
+     * Remove a previously-stored piece of data.
+     *
+     * @param name A string naming the piece of data to delete or space-separated string naming the pieces of data to delete.
+     */
+    removeData(name: string): JQuery;
+    /**
+     * Remove a previously-stored piece of data.
+     *
+     * @param list An array of strings naming the pieces of data to delete.
+     */
+    removeData(list: string[]): JQuery;
+    /**
+     * Remove all previously-stored piece of data.
+     */
+    removeData(): JQuery;
+
+    /**
+     * Return a Promise object to observe when all actions of a certain type bound to the collection, queued or not, have finished.
+     *
+     * @param type The type of queue that needs to be observed. (default: fx)
+     * @param target Object onto which the promise methods have to be attached
+     */
+    promise(type?: string, target?: Object): JQueryPromise<any>;
+
+    /**
+     * Perform a custom animation of a set of CSS properties.
+     *
+     * @param properties An object of CSS properties and values that the animation will move toward.
+     * @param duration A string or number determining how long the animation will run.
+     * @param complete A function to call once the animation is complete.
+     */
+    animate(properties: Object, duration?: string|number, complete?: Function): JQuery;
+    /**
+     * Perform a custom animation of a set of CSS properties.
+     *
+     * @param properties An object of CSS properties and values that the animation will move toward.
+     * @param duration A string or number determining how long the animation will run.
+     * @param easing A string indicating which easing function to use for the transition. (default: swing)
+     * @param complete A function to call once the animation is complete.
+     */
+    animate(properties: Object, duration?: string|number, easing?: string, complete?: Function): JQuery;
+    /**
+     * Perform a custom animation of a set of CSS properties.
+     *
+     * @param properties An object of CSS properties and values that the animation will move toward.
+     * @param options A map of additional options to pass to the method.
+     */
+    animate(properties: Object, options: JQueryAnimationOptions): JQuery;
+
+    /**
+     * Set a timer to delay execution of subsequent items in the queue.
+     *
+     * @param duration An integer indicating the number of milliseconds to delay execution of the next item in the queue.
+     * @param queueName A string containing the name of the queue. Defaults to fx, the standard effects queue.
+     */
+    delay(duration: number, queueName?: string): JQuery;
+
+    /**
+     * Display the matched elements by fading them to opaque.
+     *
+     * @param duration A string or number determining how long the animation will run.
+     * @param complete A function to call once the animation is complete.
+     */
+    fadeIn(duration?: number|string, complete?: Function): JQuery;
+    /**
+     * Display the matched elements by fading them to opaque.
+     *
+     * @param duration A string or number determining how long the animation will run.
+     * @param easing A string indicating which easing function to use for the transition.
+     * @param complete A function to call once the animation is complete.
+     */
+    fadeIn(duration?: number|string, easing?: string, complete?: Function): JQuery;
+    /**
+     * Display the matched elements by fading them to opaque.
+     *
+     * @param options A map of additional options to pass to the method.
+     */
+    fadeIn(options: JQueryAnimationOptions): JQuery;
+
+    /**
+     * Hide the matched elements by fading them to transparent.
+     *
+     * @param duration A string or number determining how long the animation will run.
+     * @param complete A function to call once the animation is complete.
+     */
+    fadeOut(duration?: number|string, complete?: Function): JQuery;
+    /**
+     * Hide the matched elements by fading them to transparent.
+     *
+     * @param duration A string or number determining how long the animation will run.
+     * @param easing A string indicating which easing function to use for the transition.
+     * @param complete A function to call once the animation is complete.
+     */
+    fadeOut(duration?: number|string, easing?: string, complete?: Function): JQuery;
+    /**
+     * Hide the matched elements by fading them to transparent.
+     *
+     * @param options A map of additional options to pass to the method.
+     */
+    fadeOut(options: JQueryAnimationOptions): JQuery;
+
+    /**
+     * Adjust the opacity of the matched elements.
+     *
+     * @param duration A string or number determining how long the animation will run.
+     * @param opacity A number between 0 and 1 denoting the target opacity.
+     * @param complete A function to call once the animation is complete.
+     */
+    fadeTo(duration: string|number, opacity: number, complete?: Function): JQuery;
+    /**
+     * Adjust the opacity of the matched elements.
+     *
+     * @param duration A string or number determining how long the animation will run.
+     * @param opacity A number between 0 and 1 denoting the target opacity.
+     * @param easing A string indicating which easing function to use for the transition.
+     * @param complete A function to call once the animation is complete.
+     */
+    fadeTo(duration: string|number, opacity: number, easing?: string, complete?: Function): JQuery;
+
+    /**
+     * Display or hide the matched elements by animating their opacity.
+     *
+     * @param duration A string or number determining how long the animation will run.
+     * @param complete A function to call once the animation is complete.
+     */
+    fadeToggle(duration?: number|string, complete?: Function): JQuery;
+    /**
+     * Display or hide the matched elements by animating their opacity.
+     *
+     * @param duration A string or number determining how long the animation will run.
+     * @param easing A string indicating which easing function to use for the transition.
+     * @param complete A function to call once the animation is complete.
+     */
+    fadeToggle(duration?: number|string, easing?: string, complete?: Function): JQuery;
+    /**
+     * Display or hide the matched elements by animating their opacity.
+     *
+     * @param options A map of additional options to pass to the method.
+     */
+    fadeToggle(options: JQueryAnimationOptions): JQuery;
+
+    /**
+     * Stop the currently-running animation, remove all queued animations, and complete all animations for the matched elements.
+     *
+     * @param queue The name of the queue in which to stop animations.
+     */
+    finish(queue?: string): JQuery;
+
+    /**
+     * Hide the matched elements.
+     *
+     * @param duration A string or number determining how long the animation will run.
+     * @param complete A function to call once the animation is complete.
+     */
+    hide(duration?: number|string, complete?: Function): JQuery;
+    /**
+     * Hide the matched elements.
+     *
+     * @param duration A string or number determining how long the animation will run.
+     * @param easing A string indicating which easing function to use for the transition.
+     * @param complete A function to call once the animation is complete.
+     */
+    hide(duration?: number|string, easing?: string, complete?: Function): JQuery;
+    /**
+     * Hide the matched elements.
+     *
+     * @param options A map of additional options to pass to the method.
+     */
+    hide(options: JQueryAnimationOptions): JQuery;
+
+    /**
+     * Display the matched elements.
+     *
+     * @param duration A string or number determining how long the animation will run.
+     * @param complete A function to call once the animation is complete.
+     */
+    show(duration?: number|string, complete?: Function): JQuery;
+    /**
+     * Display the matched elements.
+     *
+     * @param duration A string or number determining how long the animation will run.
+     * @param easing A string indicating which easing function to use for the transition.
+     * @param complete A function to call once the animation is complete.
+     */
+    show(duration?: number|string, easing?: string, complete?: Function): JQuery;
+    /**
+     * Display the matched elements.
+     *
+     * @param options A map of additional options to pass to the method.
+     */
+    show(options: JQueryAnimationOptions): JQuery;
+
+    /**
+     * Display the matched elements with a sliding motion.
+     *
+     * @param duration A string or number determining how long the animation will run.
+     * @param complete A function to call once the animation is complete.
+     */
+    slideDown(duration?: number|string, complete?: Function): JQuery;
+    /**
+     * Display the matched elements with a sliding motion.
+     *
+     * @param duration A string or number determining how long the animation will run.
+     * @param easing A string indicating which easing function to use for the transition.
+     * @param complete A function to call once the animation is complete.
+     */
+    slideDown(duration?: number|string, easing?: string, complete?: Function): JQuery;
+    /**
+     * Display the matched elements with a sliding motion.
+     *
+     * @param options A map of additional options to pass to the method.
+     */
+    slideDown(options: JQueryAnimationOptions): JQuery;
+
+    /**
+     * Display or hide the matched elements with a sliding motion.
+     *
+     * @param duration A string or number determining how long the animation will run.
+     * @param complete A function to call once the animation is complete.
+     */
+    slideToggle(duration?: number|string, complete?: Function): JQuery;
+    /**
+     * Display or hide the matched elements with a sliding motion.
+     *
+     * @param duration A string or number determining how long the animation will run.
+     * @param easing A string indicating which easing function to use for the transition.
+     * @param complete A function to call once the animation is complete.
+     */
+    slideToggle(duration?: number|string, easing?: string, complete?: Function): JQuery;
+    /**
+     * Display or hide the matched elements with a sliding motion.
+     *
+     * @param options A map of additional options to pass to the method.
+     */
+    slideToggle(options: JQueryAnimationOptions): JQuery;
+
+    /**
+     * Hide the matched elements with a sliding motion.
+     *
+     * @param duration A string or number determining how long the animation will run.
+     * @param complete A function to call once the animation is complete.
+     */
+    slideUp(duration?: number|string, complete?: Function): JQuery;
+    /**
+     * Hide the matched elements with a sliding motion.
+     *
+     * @param duration A string or number determining how long the animation will run.
+     * @param easing A string indicating which easing function to use for the transition.
+     * @param complete A function to call once the animation is complete.
+     */
+    slideUp(duration?: number|string, easing?: string, complete?: Function): JQuery;
+    /**
+     * Hide the matched elements with a sliding motion.
+     *
+     * @param options A map of additional options to pass to the method.
+     */
+    slideUp(options: JQueryAnimationOptions): JQuery;
+
+    /**
+     * Stop the currently-running animation on the matched elements.
+     *
+     * @param clearQueue A Boolean indicating whether to remove queued animation as well. Defaults to false.
+     * @param jumpToEnd A Boolean indicating whether to complete the current animation immediately. Defaults to false.
+     */
+    stop(clearQueue?: boolean, jumpToEnd?: boolean): JQuery;
+    /**
+     * Stop the currently-running animation on the matched elements.
+     *
+     * @param queue The name of the queue in which to stop animations.
+     * @param clearQueue A Boolean indicating whether to remove queued animation as well. Defaults to false.
+     * @param jumpToEnd A Boolean indicating whether to complete the current animation immediately. Defaults to false.
+     */
+    stop(queue?: string, clearQueue?: boolean, jumpToEnd?: boolean): JQuery;
+
+    /**
+     * Display or hide the matched elements.
+     *
+     * @param duration A string or number determining how long the animation will run.
+     * @param complete A function to call once the animation is complete.
+     */
+    toggle(duration?: number|string, complete?: Function): JQuery;
+    /**
+     * Display or hide the matched elements.
+     *
+     * @param duration A string or number determining how long the animation will run.
+     * @param easing A string indicating which easing function to use for the transition.
+     * @param complete A function to call once the animation is complete.
+     */
+    toggle(duration?: number|string, easing?: string, complete?: Function): JQuery;
+    /**
+     * Display or hide the matched elements.
+     *
+     * @param options A map of additional options to pass to the method.
+     */
+    toggle(options: JQueryAnimationOptions): JQuery;
+    /**
+     * Display or hide the matched elements.
+     *
+     * @param showOrHide A Boolean indicating whether to show or hide the elements.
+     */
+    toggle(showOrHide: boolean): JQuery;
+
+    /**
+     * Attach a handler to an event for the elements.
+     * 
+     * @param eventType A string containing one or more DOM event types, such as "click" or "submit," or custom event names.
+     * @param eventData An object containing data that will be passed to the event handler.
+     * @param handler A function to execute each time the event is triggered.
+     */
+    bind(eventType: string, eventData: any, handler: (eventObject: JQueryEventObject) => any): JQuery;
+    /**
+     * Attach a handler to an event for the elements.
+     * 
+     * @param eventType A string containing one or more DOM event types, such as "click" or "submit," or custom event names.
+     * @param handler A function to execute each time the event is triggered.
+     */
+    bind(eventType: string, handler: (eventObject: JQueryEventObject) => any): JQuery;
+    /**
+     * Attach a handler to an event for the elements.
+     * 
+     * @param eventType A string containing one or more DOM event types, such as "click" or "submit," or custom event names.
+     * @param eventData An object containing data that will be passed to the event handler.
+     * @param preventBubble Setting the third argument to false will attach a function that prevents the default action from occurring and stops the event from bubbling. The default is true.
+     */
+    bind(eventType: string, eventData: any, preventBubble: boolean): JQuery;
+    /**
+     * Attach a handler to an event for the elements.
+     * 
+     * @param eventType A string containing one or more DOM event types, such as "click" or "submit," or custom event names.
+     * @param preventBubble Setting the third argument to false will attach a function that prevents the default action from occurring and stops the event from bubbling. The default is true.
+     */
+    bind(eventType: string, preventBubble: boolean): JQuery;
+    /**
+     * Attach a handler to an event for the elements.
+     * 
+     * @param events An object containing one or more DOM event types and functions to execute for them.
+     */
+    bind(events: any): JQuery;
+
+    /**
+     * Trigger the "blur" event on an element
+     */
+    blur(): JQuery;
+    /**
+     * Bind an event handler to the "blur" JavaScript event
+     *
+     * @param handler A function to execute each time the event is triggered.
+     */
+    blur(handler: (eventObject: JQueryEventObject) => any): JQuery;
+    /**
+     * Bind an event handler to the "blur" JavaScript event
+     *
+     * @param eventData An object containing data that will be passed to the event handler.
+     * @param handler A function to execute each time the event is triggered.
+     */
+    blur(eventData?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery;
+
+    /**
+     * Trigger the "change" event on an element.
+     */
+    change(): JQuery;
+    /**
+     * Bind an event handler to the "change" JavaScript event
+     *
+     * @param handler A function to execute each time the event is triggered.
+     */
+    change(handler: (eventObject: JQueryEventObject) => any): JQuery;
+    /**
+     * Bind an event handler to the "change" JavaScript event
+     *
+     * @param eventData An object containing data that will be passed to the event handler.
+     * @param handler A function to execute each time the event is triggered.
+     */
+    change(eventData?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery;
+
+    /**
+     * Trigger the "click" event on an element.
+     */
+    click(): JQuery;
+    /**
+     * Bind an event handler to the "click" JavaScript event
+     *
+     * @param eventData An object containing data that will be passed to the event handler.
+     */
+    click(handler: (eventObject: JQueryEventObject) => any): JQuery;
+    /**
+     * Bind an event handler to the "click" JavaScript event
+     *
+     * @param eventData An object containing data that will be passed to the event handler.
+     * @param handler A function to execute each time the event is triggered.
+     */
+    click(eventData?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery;
+
+    /**
+     * Trigger the "dblclick" event on an element.
+     */
+    dblclick(): JQuery;
+    /**
+     * Bind an event handler to the "dblclick" JavaScript event
+     *
+     * @param handler A function to execute each time the event is triggered.
+     */
+    dblclick(handler: (eventObject: JQueryEventObject) => any): JQuery;
+    /**
+     * Bind an event handler to the "dblclick" JavaScript event
+     *
+     * @param eventData An object containing data that will be passed to the event handler.
+     * @param handler A function to execute each time the event is triggered.
+     */
+    dblclick(eventData?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery;
+
+    delegate(selector: any, eventType: string, handler: (eventObject: JQueryEventObject) => any): JQuery;
+    delegate(selector: any, eventType: string, eventData: any, handler: (eventObject: JQueryEventObject) => any): JQuery;
+
+    /**
+     * Trigger the "focus" event on an element.
+     */
+    focus(): JQuery;
+    /**
+     * Bind an event handler to the "focus" JavaScript event
+     *
+     * @param handler A function to execute each time the event is triggered.
+     */
+    focus(handler: (eventObject: JQueryEventObject) => any): JQuery;
+    /**
+     * Bind an event handler to the "focus" JavaScript event
+     *
+     * @param eventData An object containing data that will be passed to the event handler.
+     * @param handler A function to execute each time the event is triggered.
+     */
+    focus(eventData?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery;
+
+    /**
+     * Trigger the "focusin" event on an element.
+     */
+    focusin(): JQuery;
+    /**
+     * Bind an event handler to the "focusin" JavaScript event
+     *
+     * @param handler A function to execute each time the event is triggered.
+     */
+    focusin(handler: (eventObject: JQueryEventObject) => any): JQuery;
+    /**
+     * Bind an event handler to the "focusin" JavaScript event
+     *
+     * @param eventData An object containing data that will be passed to the event handler.
+     * @param handler A function to execute each time the event is triggered.
+     */
+    focusin(eventData: Object, handler: (eventObject: JQueryEventObject) => any): JQuery;
+
+    /**
+     * Trigger the "focusout" event on an element.
+     */
+    focusout(): JQuery;
+    /**
+     * Bind an event handler to the "focusout" JavaScript event
+     *
+     * @param handler A function to execute each time the event is triggered.
+     */
+    focusout(handler: (eventObject: JQueryEventObject) => any): JQuery;
+    /**
+     * Bind an event handler to the "focusout" JavaScript event
+     *
+     * @param eventData An object containing data that will be passed to the event handler.
+     * @param handler A function to execute each time the event is triggered.
+     */
+    focusout(eventData: Object, handler: (eventObject: JQueryEventObject) => any): JQuery;
+
+    /**
+     * Bind two handlers to the matched elements, to be executed when the mouse pointer enters and leaves the elements.
+     *
+     * @param handlerIn A function to execute when the mouse pointer enters the element.
+     * @param handlerOut A function to execute when the mouse pointer leaves the element.
+     */
+    hover(handlerIn: (eventObject: JQueryEventObject) => any, handlerOut: (eventObject: JQueryEventObject) => any): JQuery;
+    /**
+     * Bind a single handler to the matched elements, to be executed when the mouse pointer enters or leaves the elements.
+     *
+     * @param handlerInOut A function to execute when the mouse pointer enters or leaves the element.
+     */
+    hover(handlerInOut: (eventObject: JQueryEventObject) => any): JQuery;
+
+    /**
+     * Trigger the "keydown" event on an element.
+     */
+    keydown(): JQuery;
+    /**
+     * Bind an event handler to the "keydown" JavaScript event
+     *
+     * @param handler A function to execute each time the event is triggered.
+     */
+    keydown(handler: (eventObject: JQueryKeyEventObject) => any): JQuery;
+    /**
+     * Bind an event handler to the "keydown" JavaScript event
+     *
+     * @param eventData An object containing data that will be passed to the event handler.
+     * @param handler A function to execute each time the event is triggered.
+     */
+    keydown(eventData?: any, handler?: (eventObject: JQueryKeyEventObject) => any): JQuery;
+
+    /**
+     * Trigger the "keypress" event on an element.
+     */
+    keypress(): JQuery;
+    /**
+     * Bind an event handler to the "keypress" JavaScript event
+     *
+     * @param handler A function to execute each time the event is triggered.
+     */
+    keypress(handler: (eventObject: JQueryKeyEventObject) => any): JQuery;
+    /**
+     * Bind an event handler to the "keypress" JavaScript event
+     *
+     * @param eventData An object containing data that will be passed to the event handler.
+     * @param handler A function to execute each time the event is triggered.
+     */
+    keypress(eventData?: any, handler?: (eventObject: JQueryKeyEventObject) => any): JQuery;
+
+    /**
+     * Trigger the "keyup" event on an element.
+     */
+    keyup(): JQuery;
+    /**
+     * Bind an event handler to the "keyup" JavaScript event
+     *
+     * @param handler A function to execute each time the event is triggered.
+     */
+    keyup(handler: (eventObject: JQueryKeyEventObject) => any): JQuery;
+    /**
+     * Bind an event handler to the "keyup" JavaScript event
+     *
+     * @param eventData An object containing data that will be passed to the event handler.
+     * @param handler A function to execute each time the event is triggered.
+     */
+    keyup(eventData?: any, handler?: (eventObject: JQueryKeyEventObject) => any): JQuery;
+
+    /**
+     * Bind an event handler to the "load" JavaScript event.
+     *
+     * @param handler A function to execute when the event is triggered.
+     */
+    load(handler: (eventObject: JQueryEventObject) => any): JQuery;
+    /**
+     * Bind an event handler to the "load" JavaScript event.
+     *
+     * @param eventData An object containing data that will be passed to the event handler.
+     * @param handler A function to execute when the event is triggered.
+     */
+    load(eventData?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery;
+
+    /**
+     * Trigger the "mousedown" event on an element.
+     */
+    mousedown(): JQuery;
+    /**
+     * Bind an event handler to the "mousedown" JavaScript event.
+     *
+     * @param handler A function to execute when the event is triggered.
+     */
+    mousedown(handler: (eventObject: JQueryMouseEventObject) => any): JQuery;
+    /**
+     * Bind an event handler to the "mousedown" JavaScript event.
+     *
+     * @param eventData An object containing data that will be passed to the event handler.
+     * @param handler A function to execute when the event is triggered.
+     */
+    mousedown(eventData: Object, handler: (eventObject: JQueryMouseEventObject) => any): JQuery;
+
+    /**
+     * Trigger the "mouseenter" event on an element.
+     */
+    mouseenter(): JQuery;
+    /**
+     * Bind an event handler to be fired when the mouse enters an element.
+     *
+     * @param handler A function to execute when the event is triggered.
+     */
+    mouseenter(handler: (eventObject: JQueryMouseEventObject) => any): JQuery;
+    /**
+     * Bind an event handler to be fired when the mouse enters an element.
+     *
+     * @param eventData An object containing data that will be passed to the event handler.
+     * @param handler A function to execute when the event is triggered.
+     */
+    mouseenter(eventData: Object, handler: (eventObject: JQueryMouseEventObject) => any): JQuery;
+
+    /**
+     * Trigger the "mouseleave" event on an element.
+     */
+    mouseleave(): JQuery;
+    /**
+     * Bind an event handler to be fired when the mouse leaves an element.
+     *
+     * @param handler A function to execute when the event is triggered.
+     */
+    mouseleave(handler: (eventObject: JQueryMouseEventObject) => any): JQuery;
+    /**
+     * Bind an event handler to be fired when the mouse leaves an element.
+     *
+     * @param eventData An object containing data that will be passed to the event handler.
+     * @param handler A function to execute when the event is triggered.
+     */
+    mouseleave(eventData: Object, handler: (eventObject: JQueryMouseEventObject) => any): JQuery;
+
+    /**
+     * Trigger the "mousemove" event on an element.
+     */
+    mousemove(): JQuery;
+    /**
+     * Bind an event handler to the "mousemove" JavaScript event.
+     *
+     * @param handler A function to execute when the event is triggered.
+     */
+    mousemove(handler: (eventObject: JQueryMouseEventObject) => any): JQuery;
+    /**
+     * Bind an event handler to the "mousemove" JavaScript event.
+     *
+     * @param eventData An object containing data that will be passed to the event handler.
+     * @param handler A function to execute when the event is triggered.
+     */
+    mousemove(eventData: Object, handler: (eventObject: JQueryMouseEventObject) => any): JQuery;
+
+    /**
+     * Trigger the "mouseout" event on an element.
+     */
+    mouseout(): JQuery;
+    /**
+     * Bind an event handler to the "mouseout" JavaScript event.
+     *
+     * @param handler A function to execute when the event is triggered.
+     */
+    mouseout(handler: (eventObject: JQueryMouseEventObject) => any): JQuery;
+    /**
+     * Bind an event handler to the "mouseout" JavaScript event.
+     *
+     * @param eventData An object containing data that will be passed to the event handler.
+     * @param handler A function to execute when the event is triggered.
+     */
+    mouseout(eventData: Object, handler: (eventObject: JQueryMouseEventObject) => any): JQuery;
+
+    /**
+     * Trigger the "mouseover" event on an element.
+     */
+    mouseover(): JQuery;
+    /**
+     * Bind an event handler to the "mouseover" JavaScript event.
+     *
+     * @param handler A function to execute when the event is triggered.
+     */
+    mouseover(handler: (eventObject: JQueryMouseEventObject) => any): JQuery;
+    /**
+     * Bind an event handler to the "mouseover" JavaScript event.
+     *
+     * @param eventData An object containing data that will be passed to the event handler.
+     * @param handler A function to execute when the event is triggered.
+     */
+    mouseover(eventData: Object, handler: (eventObject: JQueryMouseEventObject) => any): JQuery;
+
+    /**
+     * Trigger the "mouseup" event on an element.
+     */
+    mouseup(): JQuery;
+    /**
+     * Bind an event handler to the "mouseup" JavaScript event.
+     *
+     * @param handler A function to execute when the event is triggered.
+     */
+    mouseup(handler: (eventObject: JQueryMouseEventObject) => any): JQuery;
+    /**
+     * Bind an event handler to the "mouseup" JavaScript event.
+     *
+     * @param eventData An object containing data that will be passed to the event handler.
+     * @param handler A function to execute when the event is triggered.
+     */
+    mouseup(eventData: Object, handler: (eventObject: JQueryMouseEventObject) => any): JQuery;
+
+    /**
+     * Remove an event handler.
+     */
+    off(): JQuery;
+    /**
+     * Remove an event handler.
+     *
+     * @param events One or more space-separated event types and optional namespaces, or just namespaces, such as "click", "keydown.myPlugin", or ".myPlugin".
+     * @param selector A selector which should match the one originally passed to .on() when attaching event handlers.
+     * @param handler A handler function previously attached for the event(s), or the special value false.
+     */
+    off(events: string, selector?: string, handler?: (eventObject: JQueryEventObject) => any): JQuery;
+    /**
+     * Remove an event handler.
+     *
+     * @param events One or more space-separated event types and optional namespaces, or just namespaces, such as "click", "keydown.myPlugin", or ".myPlugin".
+     * @param handler A handler function previously attached for the event(s), or the special value false. Takes handler with extra args that can be attached with on().
+     */
+    off(events: string, handler: (eventObject: JQueryEventObject, ...args: any[]) => any): JQuery;
+    /**
+     * Remove an event handler.
+     *
+     * @param events One or more space-separated event types and optional namespaces, or just namespaces, such as "click", "keydown.myPlugin", or ".myPlugin".
+     * @param handler A handler function previously attached for the event(s), or the special value false.
+     */
+    off(events: string, handler: (eventObject: JQueryEventObject) => any): JQuery;
+    /**
+     * Remove an event handler.
+     *
+     * @param events An object where the string keys represent one or more space-separated event types and optional namespaces, and the values represent handler functions previously attached for the event(s).
+     * @param selector A selector which should match the one originally passed to .on() when attaching event handlers.
+     */
+    off(events: { [key: string]: any; }, selector?: string): JQuery;
+
+    /**
+     * Attach an event handler function for one or more events to the selected elements.
+     *
+     * @param events One or more space-separated event types and optional namespaces, such as "click" or "keydown.myPlugin".
+     * @param handler A function to execute when the event is triggered. The value false is also allowed as a shorthand for a function that simply does return false. Rest parameter args is for optional parameters passed to jQuery.trigger(). Note that the actual parameters on the event handler function must be marked as optional (? syntax).
+     */
+    on(events: string, handler: (eventObject: JQueryEventObject, ...args: any[]) => any): JQuery;
+    /**
+     * Attach an event handler function for one or more events to the selected elements.
+     *
+     * @param events One or more space-separated event types and optional namespaces, such as "click" or "keydown.myPlugin".
+     * @param data Data to be passed to the handler in event.data when an event is triggered.
+     * @param handler A function to execute when the event is triggered. The value false is also allowed as a shorthand for a function that simply does return false.
+    */
+    on(events: string, data : any, handler: (eventObject: JQueryEventObject, ...args: any[]) => any): JQuery;
+    /**
+     * Attach an event handler function for one or more events to the selected elements.
+     *
+     * @param events One or more space-separated event types and optional namespaces, such as "click" or "keydown.myPlugin".
+     * @param selector A selector string to filter the descendants of the selected elements that trigger the event. If the selector is null or omitted, the event is always triggered when it reaches the selected element.
+     * @param handler A function to execute when the event is triggered. The value false is also allowed as a shorthand for a function that simply does return false.
+     */
+    on(events: string, selector: string, handler: (eventObject: JQueryEventObject, ...eventData: any[]) => any): JQuery;
+    /**
+     * Attach an event handler function for one or more events to the selected elements.
+     *
+     * @param events One or more space-separated event types and optional namespaces, such as "click" or "keydown.myPlugin".
+     * @param selector A selector string to filter the descendants of the selected elements that trigger the event. If the selector is null or omitted, the event is always triggered when it reaches the selected element.
+     * @param data Data to be passed to the handler in event.data when an event is triggered.
+     * @param handler A function to execute when the event is triggered. The value false is also allowed as a shorthand for a function that simply does return false.
+     */
+    on(events: string, selector: string, data: any, handler: (eventObject: JQueryEventObject, ...eventData: any[]) => any): JQuery;
+    /**
+     * Attach an event handler function for one or more events to the selected elements.
+     *
+     * @param events An object in which the string keys represent one or more space-separated event types and optional namespaces, and the values represent a handler function to be called for the event(s).
+     * @param selector A selector string to filter the descendants of the selected elements that will call the handler. If the selector is null or omitted, the handler is always called when it reaches the selected element.
+     * @param data Data to be passed to the handler in event.data when an event occurs.
+     */
+    on(events: { [key: string]: any; }, selector?: string, data?: any): JQuery;
+    /**
+     * Attach an event handler function for one or more events to the selected elements.
+     *
+     * @param events An object in which the string keys represent one or more space-separated event types and optional namespaces, and the values represent a handler function to be called for the event(s).
+     * @param data Data to be passed to the handler in event.data when an event occurs.
+     */
+    on(events: { [key: string]: any; }, data?: any): JQuery;
+
+    /**
+     * Attach a handler to an event for the elements. The handler is executed at most once per element per event type.
+     *
+     * @param events A string containing one or more JavaScript event types, such as "click" or "submit," or custom event names.
+     * @param handler A function to execute at the time the event is triggered.
+     */
+    one(events: string, handler: (eventObject: JQueryEventObject) => any): JQuery;
+    /**
+     * Attach a handler to an event for the elements. The handler is executed at most once per element per event type.
+     *
+     * @param events A string containing one or more JavaScript event types, such as "click" or "submit," or custom event names.
+     * @param data An object containing data that will be passed to the event handler.
+     * @param handler A function to execute at the time the event is triggered.
+     */
+    one(events: string, data: Object, handler: (eventObject: JQueryEventObject) => any): JQuery;
+
+    /**
+     * Attach a handler to an event for the elements. The handler is executed at most once per element per event type.
+     *
+     * @param events One or more space-separated event types and optional namespaces, such as "click" or "keydown.myPlugin".
+     * @param selector A selector string to filter the descendants of the selected elements that trigger the event. If the selector is null or omitted, the event is always triggered when it reaches the selected element.
+     * @param handler A function to execute when the event is triggered. The value false is also allowed as a shorthand for a function that simply does return false.
+     */
+    one(events: string, selector: string, handler: (eventObject: JQueryEventObject) => any): JQuery;
+    /**
+     * Attach a handler to an event for the elements. The handler is executed at most once per element per event type.
+     *
+     * @param events One or more space-separated event types and optional namespaces, such as "click" or "keydown.myPlugin".
+     * @param selector A selector string to filter the descendants of the selected elements that trigger the event. If the selector is null or omitted, the event is always triggered when it reaches the selected element.
+     * @param data Data to be passed to the handler in event.data when an event is triggered.
+     * @param handler A function to execute when the event is triggered. The value false is also allowed as a shorthand for a function that simply does return false.
+     */
+    one(events: string, selector: string, data: any, handler: (eventObject: JQueryEventObject) => any): JQuery;
+
+    /**
+     * Attach a handler to an event for the elements. The handler is executed at most once per element per event type.
+     *
+     * @param events An object in which the string keys represent one or more space-separated event types and optional namespaces, and the values represent a handler function to be called for the event(s).
+     * @param selector A selector string to filter the descendants of the selected elements that will call the handler. If the selector is null or omitted, the handler is always called when it reaches the selected element.
+     * @param data Data to be passed to the handler in event.data when an event occurs.
+     */
+    one(events: { [key: string]: any; }, selector?: string, data?: any): JQuery;
+
+    /**
+     * Attach a handler to an event for the elements. The handler is executed at most once per element per event type.
+     *
+     * @param events An object in which the string keys represent one or more space-separated event types and optional namespaces, and the values represent a handler function to be called for the event(s).
+     * @param data Data to be passed to the handler in event.data when an event occurs.
+     */
+    one(events: { [key: string]: any; }, data?: any): JQuery;
+
+
+    /**
+     * Specify a function to execute when the DOM is fully loaded.
+     *
+     * @param handler A function to execute after the DOM is ready.
+     */
+    ready(handler: (jQueryAlias?: JQueryStatic) => any): JQuery;
+
+    /**
+     * Trigger the "resize" event on an element.
+     */
+    resize(): JQuery;
+    /**
+     * Bind an event handler to the "resize" JavaScript event.
+     *
+     * @param handler A function to execute each time the event is triggered.
+     */
+    resize(handler: (eventObject: JQueryEventObject) => any): JQuery;
+    /**
+     * Bind an event handler to the "resize" JavaScript event.
+     *
+     * @param eventData An object containing data that will be passed to the event handler.
+     * @param handler A function to execute each time the event is triggered.
+     */
+    resize(eventData: Object, handler: (eventObject: JQueryEventObject) => any): JQuery;
+
+    /**
+     * Trigger the "scroll" event on an element.
+     */
+    scroll(): JQuery;
+    /**
+     * Bind an event handler to the "scroll" JavaScript event.
+     *
+     * @param handler A function to execute each time the event is triggered.
+     */
+    scroll(handler: (eventObject: JQueryEventObject) => any): JQuery;
+    /**
+     * Bind an event handler to the "scroll" JavaScript event.
+     *
+     * @param eventData An object containing data that will be passed to the event handler.
+     * @param handler A function to execute each time the event is triggered.
+     */
+    scroll(eventData: Object, handler: (eventObject: JQueryEventObject) => any): JQuery;
+
+    /**
+     * Trigger the "select" event on an element.
+     */
+    select(): JQuery;
+    /**
+     * Bind an event handler to the "select" JavaScript event.
+     *
+     * @param handler A function to execute each time the event is triggered.
+     */
+    select(handler: (eventObject: JQueryEventObject) => any): JQuery;
+    /**
+     * Bind an event handler to the "select" JavaScript event.
+     *
+     * @param eventData An object containing data that will be passed to the event handler.
+     * @param handler A function to execute each time the event is triggered.
+     */
+    select(eventData: Object, handler: (eventObject: JQueryEventObject) => any): JQuery;
+
+    /**
+     * Trigger the "submit" event on an element.
+     */
+    submit(): JQuery;
+    /**
+     * Bind an event handler to the "submit" JavaScript event
+     *
+     * @param handler A function to execute each time the event is triggered.
+     */
+    submit(handler: (eventObject: JQueryEventObject) => any): JQuery;
+    /**
+     * Bind an event handler to the "submit" JavaScript event
+     *
+     * @param eventData An object containing data that will be passed to the event handler.
+     * @param handler A function to execute each time the event is triggered.
+     */
+    submit(eventData?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery;
+
+    /**
+     * Execute all handlers and behaviors attached to the matched elements for the given event type.
+     * 
+     * @param eventType A string containing a JavaScript event type, such as click or submit.
+     * @param extraParameters Additional parameters to pass along to the event handler.
+     */
+    trigger(eventType: string, extraParameters?: any[]|Object): JQuery;
+    /**
+     * Execute all handlers and behaviors attached to the matched elements for the given event type.
+     * 
+     * @param event A jQuery.Event object.
+     * @param extraParameters Additional parameters to pass along to the event handler.
+     */
+    trigger(event: JQueryEventObject, extraParameters?: any[]|Object): JQuery;
+
+    /**
+     * Execute all handlers attached to an element for an event.
+     * 
+     * @param eventType A string containing a JavaScript event type, such as click or submit.
+     * @param extraParameters An array of additional parameters to pass along to the event handler.
+     */
+    triggerHandler(eventType: string, ...extraParameters: any[]): Object;
+
+    /**
+     * Execute all handlers attached to an element for an event.
+     * 
+     * @param event A jQuery.Event object.
+     * @param extraParameters An array of additional parameters to pass along to the event handler.
+     */
+    triggerHandler(event: JQueryEventObject, ...extraParameters: any[]): Object;
+
+    /**
+     * Remove a previously-attached event handler from the elements.
+     * 
+     * @param eventType A string containing a JavaScript event type, such as click or submit.
+     * @param handler The function that is to be no longer executed.
+     */
+    unbind(eventType?: string, handler?: (eventObject: JQueryEventObject) => any): JQuery;
+    /**
+     * Remove a previously-attached event handler from the elements.
+     * 
+     * @param eventType A string containing a JavaScript event type, such as click or submit.
+     * @param fls Unbinds the corresponding 'return false' function that was bound using .bind( eventType, false ).
+     */
+    unbind(eventType: string, fls: boolean): JQuery;
+    /**
+     * Remove a previously-attached event handler from the elements.
+     * 
+     * @param evt A JavaScript event object as passed to an event handler.
+     */
+    unbind(evt: any): JQuery;
+
+    /**
+     * Remove a handler from the event for all elements which match the current selector, based upon a specific set of root elements.
+     */
+    undelegate(): JQuery;
+    /**
+     * Remove a handler from the event for all elements which match the current selector, based upon a specific set of root elements.
+     * 
+     * @param selector A selector which will be used to filter the event results.
+     * @param eventType A string containing a JavaScript event type, such as "click" or "keydown"
+     * @param handler A function to execute at the time the event is triggered.
+     */
+    undelegate(selector: string, eventType: string, handler?: (eventObject: JQueryEventObject) => any): JQuery;
+    /**
+     * Remove a handler from the event for all elements which match the current selector, based upon a specific set of root elements.
+     * 
+     * @param selector A selector which will be used to filter the event results.
+     * @param events An object of one or more event types and previously bound functions to unbind from them.
+     */
+    undelegate(selector: string, events: Object): JQuery;
+    /**
+     * Remove a handler from the event for all elements which match the current selector, based upon a specific set of root elements.
+     * 
+     * @param namespace A string containing a namespace to unbind all events from.
+     */
+    undelegate(namespace: string): JQuery;
+
+    /**
+     * Bind an event handler to the "unload" JavaScript event. (DEPRECATED from v1.8)
+     * 
+     * @param handler A function to execute when the event is triggered.
+     */
+    unload(handler: (eventObject: JQueryEventObject) => any): JQuery;
+    /**
+     * Bind an event handler to the "unload" JavaScript event. (DEPRECATED from v1.8)
+     * 
+     * @param eventData A plain object of data that will be passed to the event handler.
+     * @param handler A function to execute when the event is triggered.
+     */
+    unload(eventData?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery;
+
+    /**
+     * The DOM node context originally passed to jQuery(); if none was passed then context will likely be the document. (DEPRECATED from v1.10)
+     */
+    context: Element;
+
+    jquery: string;
+
+    /**
+     * Bind an event handler to the "error" JavaScript event. (DEPRECATED from v1.8)
+     * 
+     * @param handler A function to execute when the event is triggered.
+     */
+    error(handler: (eventObject: JQueryEventObject) => any): JQuery;
+    /**
+     * Bind an event handler to the "error" JavaScript event. (DEPRECATED from v1.8)
+     * 
+     * @param eventData A plain object of data that will be passed to the event handler.
+     * @param handler A function to execute when the event is triggered.
+     */
+    error(eventData: any, handler: (eventObject: JQueryEventObject) => any): JQuery;
+
+    /**
+     * Add a collection of DOM elements onto the jQuery stack.
+     * 
+     * @param elements An array of elements to push onto the stack and make into a new jQuery object.
+     */
+    pushStack(elements: any[]): JQuery;
+    /**
+     * Add a collection of DOM elements onto the jQuery stack.
+     * 
+     * @param elements An array of elements to push onto the stack and make into a new jQuery object.
+     * @param name The name of a jQuery method that generated the array of elements.
+     * @param arguments The arguments that were passed in to the jQuery method (for serialization).
+     */
+    pushStack(elements: any[], name: string, arguments: any[]): JQuery;
+
+    /**
+     * Insert content, specified by the parameter, after each element in the set of matched elements.
+     * 
+     * param content1 HTML string, DOM element, array of elements, or jQuery object to insert after each element in the set of matched elements.
+     * param content2 One or more additional DOM elements, arrays of elements, HTML strings, or jQuery objects to insert after each element in the set of matched elements.
+     */
+    after(content1: JQuery|any[]|Element|Text|string, ...content2: any[]): JQuery;
+    /**
+     * Insert content, specified by the parameter, after each element in the set of matched elements.
+     * 
+     * param func A function that returns an HTML string, DOM element(s), or jQuery object to insert after each element in the set of matched elements. Receives the index position of the element in the set as an argument. Within the function, this refers to the current element in the set.
+     */
+    after(func: (index: number, html: string) => string|Element|JQuery): JQuery;
+
+    /**
+     * Insert content, specified by the parameter, to the end of each element in the set of matched elements.
+     * 
+     * param content1 DOM element, array of elements, HTML string, or jQuery object to insert at the end of each element in the set of matched elements.
+     * param content2 One or more additional DOM elements, arrays of elements, HTML strings, or jQuery objects to insert at the end of each element in the set of matched elements.
+     */
+    append(content1: JQuery|any[]|Element|Text|string, ...content2: any[]): JQuery;
+    /**
+     * Insert content, specified by the parameter, to the end of each element in the set of matched elements.
+     * 
+     * param func A function that returns an HTML string, DOM element(s), or jQuery object to insert at the end of each element in the set of matched elements. Receives the index position of the element in the set and the old HTML value of the element as arguments. Within the function, this refers to the current element in the set.
+     */
+    append(func: (index: number, html: string) => string|Element|JQuery): JQuery;
+
+    /**
+     * Insert every element in the set of matched elements to the end of the target.
+     * 
+     * @param target A selector, element, HTML string, array of elements, or jQuery object; the matched set of elements will be inserted at the end of the element(s) specified by this parameter.
+     */
+    appendTo(target: JQuery|any[]|Element|string): JQuery;
+
+    /**
+     * Insert content, specified by the parameter, before each element in the set of matched elements.
+     * 
+     * param content1 HTML string, DOM element, array of elements, or jQuery object to insert before each element in the set of matched elements.
+     * param content2 One or more additional DOM elements, arrays of elements, HTML strings, or jQuery objects to insert before each element in the set of matched elements.
+     */
+    before(content1: JQuery|any[]|Element|Text|string, ...content2: any[]): JQuery;
+    /**
+     * Insert content, specified by the parameter, before each element in the set of matched elements.
+     * 
+     * param func A function that returns an HTML string, DOM element(s), or jQuery object to insert before each element in the set of matched elements. Receives the index position of the element in the set as an argument. Within the function, this refers to the current element in the set.
+     */
+    before(func: (index: number, html: string) => string|Element|JQuery): JQuery;
+
+    /**
+     * Create a deep copy of the set of matched elements.
+     * 
+     * param withDataAndEvents A Boolean indicating whether event handlers and data should be copied along with the elements. The default value is false.
+     * param deepWithDataAndEvents A Boolean indicating whether event handlers and data for all children of the cloned element should be copied. By default its value matches the first argument's value (which defaults to false).
+     */
+    clone(withDataAndEvents?: boolean, deepWithDataAndEvents?: boolean): JQuery;
+
+    /**
+     * Remove the set of matched elements from the DOM.
+     * 
+     * param selector A selector expression that filters the set of matched elements to be removed.
+     */
+    detach(selector?: string): JQuery;
+
+    /**
+     * Remove all child nodes of the set of matched elements from the DOM.
+     */
+    empty(): JQuery;
+
+    /**
+     * Insert every element in the set of matched elements after the target.
+     * 
+     * param target A selector, element, array of elements, HTML string, or jQuery object; the matched set of elements will be inserted after the element(s) specified by this parameter.
+     */
+    insertAfter(target: JQuery|any[]|Element|Text|string): JQuery;
+
+    /**
+     * Insert every element in the set of matched elements before the target.
+     * 
+     * param target A selector, element, array of elements, HTML string, or jQuery object; the matched set of elements will be inserted before the element(s) specified by this parameter.
+     */
+    insertBefore(target: JQuery|any[]|Element|Text|string): JQuery;
+
+    /**
+     * Insert content, specified by the parameter, to the beginning of each element in the set of matched elements.
+     * 
+     * param content1 DOM element, array of elements, HTML string, or jQuery object to insert at the beginning of each element in the set of matched elements.
+     * param content2 One or more additional DOM elements, arrays of elements, HTML strings, or jQuery objects to insert at the beginning of each element in the set of matched elements.
+     */
+    prepend(content1: JQuery|any[]|Element|Text|string, ...content2: any[]): JQuery;
+    /**
+     * Insert content, specified by the parameter, to the beginning of each element in the set of matched elements.
+     * 
+     * param func A function that returns an HTML string, DOM element(s), or jQuery object to insert at the beginning of each element in the set of matched elements. Receives the index position of the element in the set and the old HTML value of the element as arguments. Within the function, this refers to the current element in the set.
+     */
+    prepend(func: (index: number, html: string) => string|Element|JQuery): JQuery;
+
+    /**
+     * Insert every element in the set of matched elements to the beginning of the target.
+     * 
+     * @param target A selector, element, HTML string, array of elements, or jQuery object; the matched set of elements will be inserted at the beginning of the element(s) specified by this parameter.
+     */
+    prependTo(target: JQuery|any[]|Element|string): JQuery;
+
+    /**
+     * Remove the set of matched elements from the DOM.
+     * 
+     * @param selector A selector expression that filters the set of matched elements to be removed.
+     */
+    remove(selector?: string): JQuery;
+
+    /**
+     * Replace each target element with the set of matched elements.
+     * 
+     * @param target A selector string, jQuery object, DOM element, or array of elements indicating which element(s) to replace.
+     */
+    replaceAll(target: JQuery|any[]|Element|string): JQuery;
+
+    /**
+     * Replace each element in the set of matched elements with the provided new content and return the set of elements that was removed.
+     * 
+     * param newContent The content to insert. May be an HTML string, DOM element, array of DOM elements, or jQuery object.
+     */
+    replaceWith(newContent: JQuery|any[]|Element|Text|string): JQuery;
+    /**
+     * Replace each element in the set of matched elements with the provided new content and return the set of elements that was removed.
+     * 
+     * param func A function that returns content with which to replace the set of matched elements.
+     */
+    replaceWith(func: () => Element|JQuery): JQuery;
+
+    /**
+     * Get the combined text contents of each element in the set of matched elements, including their descendants.
+     */
+    text(): string;
+    /**
+     * Set the content of each element in the set of matched elements to the specified text.
+     * 
+     * @param text The text to set as the content of each matched element. When Number or Boolean is supplied, it will be converted to a String representation.
+     */
+    text(text: string|number|boolean): JQuery;
+    /**
+     * Set the content of each element in the set of matched elements to the specified text.
+     * 
+     * @param func A function returning the text content to set. Receives the index position of the element in the set and the old text value as arguments.
+     */
+    text(func: (index: number, text: string) => string): JQuery;
+
+    /**
+     * Retrieve all the elements contained in the jQuery set, as an array.
+     */
+    toArray(): any[];
+
+    /**
+     * Remove the parents of the set of matched elements from the DOM, leaving the matched elements in their place.
+     */
+    unwrap(): JQuery;
+
+    /**
+     * Wrap an HTML structure around each element in the set of matched elements.
+     * 
+     * @param wrappingElement A selector, element, HTML string, or jQuery object specifying the structure to wrap around the matched elements.
+     */
+    wrap(wrappingElement: JQuery|Element|string): JQuery;
+    /**
+     * Wrap an HTML structure around each element in the set of matched elements.
+     * 
+     * @param func A callback function returning the HTML content or jQuery object to wrap around the matched elements. Receives the index position of the element in the set as an argument. Within the function, this refers to the current element in the set.
+     */
+    wrap(func: (index: number) => string|JQuery): JQuery;
+
+    /**
+     * Wrap an HTML structure around all elements in the set of matched elements.
+     * 
+     * @param wrappingElement A selector, element, HTML string, or jQuery object specifying the structure to wrap around the matched elements.
+     */
+    wrapAll(wrappingElement: JQuery|Element|string): JQuery;
+    wrapAll(func: (index: number) => string): JQuery;
+
+    /**
+     * Wrap an HTML structure around the content of each element in the set of matched elements.
+     * 
+     * @param wrappingElement An HTML snippet, selector expression, jQuery object, or DOM element specifying the structure to wrap around the content of the matched elements.
+     */
+    wrapInner(wrappingElement: JQuery|Element|string): JQuery;
+    /**
+     * Wrap an HTML structure around the content of each element in the set of matched elements.
+     * 
+     * @param func A callback function which generates a structure to wrap around the content of the matched elements. Receives the index position of the element in the set as an argument. Within the function, this refers to the current element in the set.
+     */
+    wrapInner(func: (index: number) => string): JQuery;
+
+    /**
+     * Iterate over a jQuery object, executing a function for each matched element.
+     * 
+     * @param func A function to execute for each matched element.
+     */
+    each(func: (index: number, elem: Element) => any): JQuery;
+
+    /**
+     * Retrieve one of the elements matched by the jQuery object.
+     * 
+     * @param index A zero-based integer indicating which element to retrieve.
+     */
+    get(index: number): HTMLElement;
+    /**
+     * Retrieve the elements matched by the jQuery object.
+     */
+    get(): any[];
+
+    /**
+     * Search for a given element from among the matched elements.
+     */
+    index(): number;
+    /**
+     * Search for a given element from among the matched elements.
+     * 
+     * @param selector A selector representing a jQuery collection in which to look for an element.
+     */
+    index(selector: string|JQuery|Element): number;
+
+    /**
+     * The number of elements in the jQuery object.
+     */
+    length: number;
+    /**
+     * A selector representing selector passed to jQuery(), if any, when creating the original set.
+     * version deprecated: 1.7, removed: 1.9
+     */
+    selector: string;
+    [index: string]: any;
+    [index: number]: HTMLElement;
+
+    /**
+     * Add elements to the set of matched elements.
+     * 
+     * @param selector A string representing a selector expression to find additional elements to add to the set of matched elements.
+     * @param context The point in the document at which the selector should begin matching; similar to the context argument of the $(selector, context) method.
+     */
+    add(selector: string, context?: Element): JQuery;
+    /**
+     * Add elements to the set of matched elements.
+     * 
+     * @param elements One or more elements to add to the set of matched elements.
+     */
+    add(...elements: Element[]): JQuery;
+    /**
+     * Add elements to the set of matched elements.
+     * 
+     * @param html An HTML fragment to add to the set of matched elements.
+     */
+    add(html: string): JQuery;
+    /**
+     * Add elements to the set of matched elements.
+     * 
+     * @param obj An existing jQuery object to add to the set of matched elements.
+     */
+    add(obj: JQuery): JQuery;
+
+    /**
+     * Get the children of each element in the set of matched elements, optionally filtered by a selector.
+     * 
+     * @param selector A string containing a selector expression to match elements against.
+     */
+    children(selector?: string): JQuery;
+
+    /**
+     * For each element in the set, get the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree.
+     * 
+     * @param selector A string containing a selector expression to match elements against.
+     */
+    closest(selector: string): JQuery;
+    /**
+     * For each element in the set, get the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree.
+     * 
+     * @param selector A string containing a selector expression to match elements against.
+     * @param context A DOM element within which a matching element may be found. If no context is passed in then the context of the jQuery set will be used instead.
+     */
+    closest(selector: string, context?: Element): JQuery;
+    /**
+     * For each element in the set, get the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree.
+     * 
+     * @param obj A jQuery object to match elements against.
+     */
+    closest(obj: JQuery): JQuery;
+    /**
+     * For each element in the set, get the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree.
+     * 
+     * @param element An element to match elements against.
+     */
+    closest(element: Element): JQuery;
+
+    /**
+     * Get an array of all the elements and selectors matched against the current element up through the DOM tree.
+     * 
+     * @param selectors An array or string containing a selector expression to match elements against (can also be a jQuery object).
+     * @param context A DOM element within which a matching element may be found. If no context is passed in then the context of the jQuery set will be used instead.
+     */
+    closest(selectors: any, context?: Element): any[];
+
+    /**
+     * Get the children of each element in the set of matched elements, including text and comment nodes.
+     */
+    contents(): JQuery;
+
+    /**
+     * End the most recent filtering operation in the current chain and return the set of matched elements to its previous state.
+     */
+    end(): JQuery;
+
+    /**
+     * Reduce the set of matched elements to the one at the specified index.
+     * 
+     * @param index An integer indicating the 0-based position of the element. OR An integer indicating the position of the element, counting backwards from the last element in the set.
+     *  
+     */
+    eq(index: number): JQuery;
+
+    /**
+     * Reduce the set of matched elements to those that match the selector or pass the function's test.
+     * 
+     * @param selector A string containing a selector expression to match the current set of elements against.
+     */
+    filter(selector: string): JQuery;
+    /**
+     * Reduce the set of matched elements to those that match the selector or pass the function's test.
+     * 
+     * @param func A function used as a test for each element in the set. this is the current DOM element.
+     */
+    filter(func: (index: number, element: Element) => any): JQuery;
+    /**
+     * Reduce the set of matched elements to those that match the selector or pass the function's test.
+     * 
+     * @param element An element to match the current set of elements against.
+     */
+    filter(element: Element): JQuery;
+    /**
+     * Reduce the set of matched elements to those that match the selector or pass the function's test.
+     * 
+     * @param obj An existing jQuery object to match the current set of elements against.
+     */
+    filter(obj: JQuery): JQuery;
+
+    /**
+     * Get the descendants of each element in the current set of matched elements, filtered by a selector, jQuery object, or element.
+     * 
+     * @param selector A string containing a selector expression to match elements against.
+     */
+    find(selector: string): JQuery;
+    /**
+     * Get the descendants of each element in the current set of matched elements, filtered by a selector, jQuery object, or element.
+     * 
+     * @param element An element to match elements against.
+     */
+    find(element: Element): JQuery;
+    /**
+     * Get the descendants of each element in the current set of matched elements, filtered by a selector, jQuery object, or element.
+     * 
+     * @param obj A jQuery object to match elements against.
+     */
+    find(obj: JQuery): JQuery;
+
+    /**
+     * Reduce the set of matched elements to the first in the set.
+     */
+    first(): JQuery;
+
+    /**
+     * Reduce the set of matched elements to those that have a descendant that matches the selector or DOM element.
+     * 
+     * @param selector A string containing a selector expression to match elements against.
+     */
+    has(selector: string): JQuery;
+    /**
+     * Reduce the set of matched elements to those that have a descendant that matches the selector or DOM element.
+     * 
+     * @param contained A DOM element to match elements against.
+     */
+    has(contained: Element): JQuery;
+
+    /**
+     * Check the current matched set of elements against a selector, element, or jQuery object and return true if at least one of these elements matches the given arguments.
+     * 
+     * @param selector A string containing a selector expression to match elements against.
+     */
+    is(selector: string): boolean;
+    /**
+     * Check the current matched set of elements against a selector, element, or jQuery object and return true if at least one of these elements matches the given arguments.
+     * 
+     * @param func A function used as a test for the set of elements. It accepts one argument, index, which is the element's index in the jQuery collection.Within the function, this refers to the current DOM element.
+     */
+    is(func: (index: number, element: Element) => boolean): boolean;
+    /**
+     * Check the current matched set of elements against a selector, element, or jQuery object and return true if at least one of these elements matches the given arguments.
+     * 
+     * @param obj An existing jQuery object to match the current set of elements against.
+     */
+    is(obj: JQuery): boolean;
+    /**
+     * Check the current matched set of elements against a selector, element, or jQuery object and return true if at least one of these elements matches the given arguments.
+     * 
+     * @param elements One or more elements to match the current set of elements against.
+     */
+    is(elements: any): boolean;
+
+    /**
+     * Reduce the set of matched elements to the final one in the set.
+     */
+    last(): JQuery;
+
+    /**
+     * Pass each element in the current matched set through a function, producing a new jQuery object containing the return values.
+     * 
+     * @param callback A function object that will be invoked for each element in the current set.
+     */
+    map(callback: (index: number, domElement: Element) => any): JQuery;
+
+    /**
+     * Get the immediately following sibling of each element in the set of matched elements. If a selector is provided, it retrieves the next sibling only if it matches that selector.
+     * 
+     * @param selector A string containing a selector expression to match elements against.
+     */
+    next(selector?: string): JQuery;
+
+    /**
+     * Get all following siblings of each element in the set of matched elements, optionally filtered by a selector.
+     * 
+     * @param selector A string containing a selector expression to match elements against.
+     */
+    nextAll(selector?: string): JQuery;
+
+    /**
+     * Get all following siblings of each element up to but not including the element matched by the selector, DOM node, or jQuery object passed.
+     * 
+     * @param selector A string containing a selector expression to indicate where to stop matching following sibling elements.
+     * @param filter A string containing a selector expression to match elements against.
+     */
+    nextUntil(selector?: string, filter?: string): JQuery;
+    /**
+     * Get all following siblings of each element up to but not including the element matched by the selector, DOM node, or jQuery object passed.
+     * 
+     * @param element A DOM node or jQuery object indicating where to stop matching following sibling elements.
+     * @param filter A string containing a selector expression to match elements against.
+     */
+    nextUntil(element?: Element, filter?: string): JQuery;
+    /**
+     * Get all following siblings of each element up to but not including the element matched by the selector, DOM node, or jQuery object passed.
+     * 
+     * @param obj A DOM node or jQuery object indicating where to stop matching following sibling elements.
+     * @param filter A string containing a selector expression to match elements against.
+     */
+    nextUntil(obj?: JQuery, filter?: string): JQuery;
+
+    /**
+     * Remove elements from the set of matched elements.
+     * 
+     * @param selector A string containing a selector expression to match elements against.
+     */
+    not(selector: string): JQuery;
+    /**
+     * Remove elements from the set of matched elements.
+     * 
+     * @param func A function used as a test for each element in the set. this is the current DOM element.
+     */
+    not(func: (index: number, element: Element) => boolean): JQuery;
+    /**
+     * Remove elements from the set of matched elements.
+     * 
+     * @param elements One or more DOM elements to remove from the matched set.
+     */
+    not(elements: Element|Element[]): JQuery;
+    /**
+     * Remove elements from the set of matched elements.
+     * 
+     * @param obj An existing jQuery object to match the current set of elements against.
+     */
+    not(obj: JQuery): JQuery;
+
+    /**
+     * Get the closest ancestor element that is positioned.
+     */
+    offsetParent(): JQuery;
+
+    /**
+     * Get the parent of each element in the current set of matched elements, optionally filtered by a selector.
+     * 
+     * @param selector A string containing a selector expression to match elements against.
+     */
+    parent(selector?: string): JQuery;
+
+    /**
+     * Get the ancestors of each element in the current set of matched elements, optionally filtered by a selector.
+     * 
+     * @param selector A string containing a selector expression to match elements against.
+     */
+    parents(selector?: string): JQuery;
+
+    /**
+     * Get the ancestors of each element in the current set of matched elements, up to but not including the element matched by the selector, DOM node, or jQuery object.
+     * 
+     * @param selector A string containing a selector expression to indicate where to stop matching ancestor elements.
+     * @param filter A string containing a selector expression to match elements against.
+     */
+    parentsUntil(selector?: string, filter?: string): JQuery;
+    /**
+     * Get the ancestors of each element in the current set of matched elements, up to but not including the element matched by the selector, DOM node, or jQuery object.
+     * 
+     * @param element A DOM node or jQuery object indicating where to stop matching ancestor elements.
+     * @param filter A string containing a selector expression to match elements against.
+     */
+    parentsUntil(element?: Element, filter?: string): JQuery;
+    /**
+     * Get the ancestors of each element in the current set of matched elements, up to but not including the element matched by the selector, DOM node, or jQuery object.
+     * 
+     * @param obj A DOM node or jQuery object indicating where to stop matching ancestor elements.
+     * @param filter A string containing a selector expression to match elements against.
+     */
+    parentsUntil(obj?: JQuery, filter?: string): JQuery;
+
+    /**
+     * Get the immediately preceding sibling of each element in the set of matched elements, optionally filtered by a selector.
+     * 
+     * @param selector A string containing a selector expression to match elements against.
+     */
+    prev(selector?: string): JQuery;
+
+    /**
+     * Get all preceding siblings of each element in the set of matched elements, optionally filtered by a selector.
+     * 
+     * @param selector A string containing a selector expression to match elements against.
+     */
+    prevAll(selector?: string): JQuery;
+
+    /**
+     * Get all preceding siblings of each element up to but not including the element matched by the selector, DOM node, or jQuery object.
+     * 
+     * @param selector A string containing a selector expression to indicate where to stop matching preceding sibling elements.
+     * @param filter A string containing a selector expression to match elements against.
+     */
+    prevUntil(selector?: string, filter?: string): JQuery;
+    /**
+     * Get all preceding siblings of each element up to but not including the element matched by the selector, DOM node, or jQuery object.
+     * 
+     * @param element A DOM node or jQuery object indicating where to stop matching preceding sibling elements.
+     * @param filter A string containing a selector expression to match elements against.
+     */
+    prevUntil(element?: Element, filter?: string): JQuery;
+    /**
+     * Get all preceding siblings of each element up to but not including the element matched by the selector, DOM node, or jQuery object.
+     * 
+     * @param obj A DOM node or jQuery object indicating where to stop matching preceding sibling elements.
+     * @param filter A string containing a selector expression to match elements against.
+     */
+    prevUntil(obj?: JQuery, filter?: string): JQuery;
+
+    /**
+     * Get the siblings of each element in the set of matched elements, optionally filtered by a selector.
+     * 
+     * @param selector A string containing a selector expression to match elements against.
+     */
+    siblings(selector?: string): JQuery;
+
+    /**
+     * Reduce the set of matched elements to a subset specified by a range of indices.
+     * 
+     * @param start An integer indicating the 0-based position at which the elements begin to be selected. If negative, it indicates an offset from the end of the set.
+     * @param end An integer indicating the 0-based position at which the elements stop being selected. If negative, it indicates an offset from the end of the set. If omitted, the range continues until the end of the set.
+     */
+    slice(start: number, end?: number): JQuery;
+
+    /**
+     * Show the queue of functions to be executed on the matched elements.
+     * 
+     * @param queueName A string containing the name of the queue. Defaults to fx, the standard effects queue.
+     */
+    queue(queueName?: string): any[];
+    /**
+     * Manipulate the queue of functions to be executed, once for each matched element.
+     * 
+     * @param newQueue An array of functions to replace the current queue contents.
+     */
+    queue(newQueue: Function[]): JQuery;
+    /**
+     * Manipulate the queue of functions to be executed, once for each matched element.
+     * 
+     * @param callback The new function to add to the queue, with a function to call that will dequeue the next item.
+     */
+    queue(callback: Function): JQuery;
+    /**
+     * Manipulate the queue of functions to be executed, once for each matched element.
+     * 
+     * @param queueName A string containing the name of the queue. Defaults to fx, the standard effects queue.
+     * @param newQueue An array of functions to replace the current queue contents.
+     */
+    queue(queueName: string, newQueue: Function[]): JQuery;
+    /**
+     * Manipulate the queue of functions to be executed, once for each matched element.
+     * 
+     * @param queueName A string containing the name of the queue. Defaults to fx, the standard effects queue.
+     * @param callback The new function to add to the queue, with a function to call that will dequeue the next item.
+     */
+    queue(queueName: string, callback: Function): JQuery;
+}
+declare module "jquery" {
+    export = $;
+}
+declare var jQuery: JQueryStatic;
+declare var $: JQueryStatic;

+ 1 - 0
jsconfig.json

@@ -0,0 +1 @@
+{}

+ 78 - 0
www/init.sql

@@ -0,0 +1,78 @@
+-- User Table
+CREATE TABLE IF NOT EXISTS 'user' (
+    'id' INTEGER NOT NULL  PRIMARY KEY AUTOINCREMENT,
+    'remote_id' INTEGER DEFAULT 0,
+    'host' VARCHAR(35) DEFAULT 0,
+    'port' INTEGER DEFAULT 0,
+    'database' VARCHAR(35) DEFAULT NULL,
+    'username' VARCHAR(35) DEFAULT NULL,
+    'password' VARCHAR(35) DEFAULT NULL
+);
+
+-- Partner Table
+CREATE TABLE IF NOT EXISTS 'partner' (
+    'id' INTEGER DEFAULT NULL PRIMARY KEY AUTOINCREMENT,
+    'remote_id' INTEGER DEFAULT 0,
+    'modified' INTEGER DEFAULT 0,
+    'modifiedDate' TEXT DEFAULT CURRENT_TIMESTAMP,
+    'name' VARCHAR(35) DEFAULT NULL,
+    'city' VARCHAR(35) DEFAULT NULL,
+    'mobile' VARCHAR(20) DEFAULT NULL,
+    'phone' VARCHAR(20) DEFAULT NULL,
+    'fax' VARCHAR(20) DEFAULT NULL,
+    'email' VARCHAR(100) DEFAULT NULL,
+    'street' VARCHAR(35) DEFAULT NULL,
+    'street2' VARCHAR(35) DEFAULT NULL,
+    'image_medium' BLOB DEFAULT NULL,
+    'image_small' BLOB DEFAULT NULL,
+    'comment' VARCHAR(160) DEFAULT NULL,
+    'customer' INTEGER DEFAULT 0,
+    'employee' INTEGER DEFAULT 0,
+    'is_company' INTEGER DEFAULT 0,
+    'debit' INTEGER DEFAULT 0,
+    'debit_limit' INTEGER DEFAULT 0,
+    'opportunity_count' INTEGER DEFAULT 0,
+    'contracts_count' INTEGER DEFAULT 0,
+    'journal_item_count' INTEGER DEFAULT 0,
+    'meeting_count' INTEGER DEFAULT 0,
+    'phonecall_count' INTEGER DEFAULT 0,
+    'sale_order_count' INTEGER DEFAULT NULL,
+    'total_invoiced' INTEGER DEFAULT NULL
+);
+
+-- Lead Table
+CREATE TABLE IF NOT EXISTS 'lead' (
+    'id' INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+    'remote_id' INTEGER DEFAULT 0,
+    'modified' INTEGER DEFAULT 0,
+    'modifiedDate' TEXT DEFAULT CURRENT_TIMESTAMP,
+    'name' VARCHAR(35) DEFAULT NULL,
+    'description' VARCHAR(100) DEFAULT NULL,
+    'contact_name' VARCHAR(100) DEFAULT NULL,
+    'phone' VARCHAR(20) DEFAULT NULL,
+    'mobile' VARCHAR(20) DEFAULT NULL,
+    'fax' VARCHAR(20) DEFAULT NULL,
+    'street' VARCHAR(35) DEFAULT NULL,
+    'street2' VARCHAR(35) DEFAULT NULL,
+    'meeting_count' INTEGER DEFAULT 0,
+    'message_bounce' INTEGER DEFAULT 0,
+    'planned_cost' INTEGER DEFAULT 0,
+    'planned_revenue' INTEGER DEFAULT 0,
+    'priority' INTEGER DEFAULT 0,
+    'probability' INTEGER DEFAULT 0,
+    'type' VARCHAR(20) DEFAULT NULL,
+    'stage_id' INTEGER DEFAULT NULL,
+    'user_id' INTEGER DEFAULT 0,
+    'partner_id' INTEGER DEFAULT 0
+);
+
+-- CRM Stages Table
+CREATE TABLE IF NOT EXISTS 'crm_stage' (
+    'id' INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+    'remote_id' INTEGER DEFAULT 0,
+    'modified' INTEGER DEFAULT 0,
+    'modified_date' TEXT DEFAULT CURRENT_TIMESTAMP,
+    'name' VARCHAR(35) DEFAULT NULL,
+    'probability' REAL DEFAULT 0,
+    'type' VARCHAR(100) DEFAULT 'both'
+);

+ 2 - 2
www/js/controllers/configuration.controller.js

@@ -6,7 +6,7 @@ angular.module('odoo')
     $ionicLoading,
     $ionicPopup,
     userStorageFactory,
-    odooInteroperabilityFactory,
+    odooFactory,
     sqlFactory
 ) {
 
@@ -23,7 +23,7 @@ angular.module('odoo')
 
         sqlFactory.count('user', function (total) {
             if (total == 0) {
-                odooInteroperabilityFactory.auth($scope.config, function (user) {
+                odooFactory.auth($scope.config, function (user) {
                     userStorageFactory.save(
                         [
                             user.id,

+ 50 - 0
www/js/factories/preferences.factory.js

@@ -0,0 +1,50 @@
+angular.module('odoo')
+
+    /**
+     *
+     */
+    .factory('preferencesFactory', function ($cordovaPreferences) {
+
+        /**
+         *
+         */
+        var save = function (key, value, encrypt, success, error) {
+            if (encrypt) {
+                value = '';
+            }
+
+            $cordovaPreferences.store(key, value).success(function (value) { 
+                success(value);
+            }).error(function (err) {
+                error(err);
+            });
+        }
+        
+        /**
+         *
+         */
+        var get = function (key, success, error) {
+            $cordovaPreferences.fetch(key).success(function (value) { 
+                success(value);
+            }).error(function (err) {
+                error(err);
+            });
+        }
+
+        /**
+         *
+         */
+        var remove = function (key, success, error) {
+            $cordovaPreferences.remove(key).success(function (value) { 
+                success(value);
+            }).error(function (err) {
+                error(err);
+            });
+        }
+
+        return {
+            save: save,
+            get: get,
+            remove: remove
+        }
+    });

+ 20 - 0
www/lib/angular-animate/.bower.json

@@ -0,0 +1,20 @@
+{
+  "name": "angular-animate",
+  "version": "1.5.3",
+  "license": "MIT",
+  "main": "./angular-animate.js",
+  "ignore": [],
+  "dependencies": {
+    "angular": "1.5.3"
+  },
+  "homepage": "https://github.com/angular/bower-angular-animate",
+  "_release": "1.5.3",
+  "_resolution": {
+    "type": "version",
+    "tag": "v1.5.3",
+    "commit": "671c738980fb0509b2b494716ccd8c004c39f368"
+  },
+  "_source": "https://github.com/angular/bower-angular-animate.git",
+  "_target": "1.5.3",
+  "_originalSource": "angular-animate"
+}

+ 68 - 0
www/lib/angular-animate/README.md

@@ -0,0 +1,68 @@
+# packaged angular-animate
+
+This repo is for distribution on `npm` and `bower`. The source for this module is in the
+[main AngularJS repo](https://github.com/angular/angular.js/tree/master/src/ngAnimate).
+Please file issues and pull requests against that repo.
+
+## Install
+
+You can install this package either with `npm` or with `bower`.
+
+### npm
+
+```shell
+npm install angular-animate
+```
+
+Then add `ngAnimate` as a dependency for your app:
+
+```javascript
+angular.module('myApp', [require('angular-animate')]);
+```
+
+### bower
+
+```shell
+bower install angular-animate
+```
+
+Then add a `<script>` to your `index.html`:
+
+```html
+<script src="/bower_components/angular-animate/angular-animate.js"></script>
+```
+
+Then add `ngAnimate` as a dependency for your app:
+
+```javascript
+angular.module('myApp', ['ngAnimate']);
+```
+
+## Documentation
+
+Documentation is available on the
+[AngularJS docs site](http://docs.angularjs.org/api/ngAnimate).
+
+## License
+
+The MIT License
+
+Copyright (c) 2010-2015 Google, Inc. http://angularjs.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

+ 4115 - 0
www/lib/angular-animate/angular-animate.js

@@ -0,0 +1,4115 @@
+/**
+ * @license AngularJS v1.5.3
+ * (c) 2010-2016 Google, Inc. http://angularjs.org
+ * License: MIT
+ */
+(function(window, angular, undefined) {'use strict';
+
+/* jshint ignore:start */
+var noop        = angular.noop;
+var copy        = angular.copy;
+var extend      = angular.extend;
+var jqLite      = angular.element;
+var forEach     = angular.forEach;
+var isArray     = angular.isArray;
+var isString    = angular.isString;
+var isObject    = angular.isObject;
+var isUndefined = angular.isUndefined;
+var isDefined   = angular.isDefined;
+var isFunction  = angular.isFunction;
+var isElement   = angular.isElement;
+
+var ELEMENT_NODE = 1;
+var COMMENT_NODE = 8;
+
+var ADD_CLASS_SUFFIX = '-add';
+var REMOVE_CLASS_SUFFIX = '-remove';
+var EVENT_CLASS_PREFIX = 'ng-';
+var ACTIVE_CLASS_SUFFIX = '-active';
+var PREPARE_CLASS_SUFFIX = '-prepare';
+
+var NG_ANIMATE_CLASSNAME = 'ng-animate';
+var NG_ANIMATE_CHILDREN_DATA = '$$ngAnimateChildren';
+
+// Detect proper transitionend/animationend event names.
+var CSS_PREFIX = '', TRANSITION_PROP, TRANSITIONEND_EVENT, ANIMATION_PROP, ANIMATIONEND_EVENT;
+
+// If unprefixed events are not supported but webkit-prefixed are, use the latter.
+// Otherwise, just use W3C names, browsers not supporting them at all will just ignore them.
+// Note: Chrome implements `window.onwebkitanimationend` and doesn't implement `window.onanimationend`
+// but at the same time dispatches the `animationend` event and not `webkitAnimationEnd`.
+// Register both events in case `window.onanimationend` is not supported because of that,
+// do the same for `transitionend` as Safari is likely to exhibit similar behavior.
+// Also, the only modern browser that uses vendor prefixes for transitions/keyframes is webkit
+// therefore there is no reason to test anymore for other vendor prefixes:
+// http://caniuse.com/#search=transition
+if (isUndefined(window.ontransitionend) && isDefined(window.onwebkittransitionend)) {
+  CSS_PREFIX = '-webkit-';
+  TRANSITION_PROP = 'WebkitTransition';
+  TRANSITIONEND_EVENT = 'webkitTransitionEnd transitionend';
+} else {
+  TRANSITION_PROP = 'transition';
+  TRANSITIONEND_EVENT = 'transitionend';
+}
+
+if (isUndefined(window.onanimationend) && isDefined(window.onwebkitanimationend)) {
+  CSS_PREFIX = '-webkit-';
+  ANIMATION_PROP = 'WebkitAnimation';
+  ANIMATIONEND_EVENT = 'webkitAnimationEnd animationend';
+} else {
+  ANIMATION_PROP = 'animation';
+  ANIMATIONEND_EVENT = 'animationend';
+}
+
+var DURATION_KEY = 'Duration';
+var PROPERTY_KEY = 'Property';
+var DELAY_KEY = 'Delay';
+var TIMING_KEY = 'TimingFunction';
+var ANIMATION_ITERATION_COUNT_KEY = 'IterationCount';
+var ANIMATION_PLAYSTATE_KEY = 'PlayState';
+var SAFE_FAST_FORWARD_DURATION_VALUE = 9999;
+
+var ANIMATION_DELAY_PROP = ANIMATION_PROP + DELAY_KEY;
+var ANIMATION_DURATION_PROP = ANIMATION_PROP + DURATION_KEY;
+var TRANSITION_DELAY_PROP = TRANSITION_PROP + DELAY_KEY;
+var TRANSITION_DURATION_PROP = TRANSITION_PROP + DURATION_KEY;
+
+var isPromiseLike = function(p) {
+  return p && p.then ? true : false;
+};
+
+var ngMinErr = angular.$$minErr('ng');
+function assertArg(arg, name, reason) {
+  if (!arg) {
+    throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required"));
+  }
+  return arg;
+}
+
+function mergeClasses(a,b) {
+  if (!a && !b) return '';
+  if (!a) return b;
+  if (!b) return a;
+  if (isArray(a)) a = a.join(' ');
+  if (isArray(b)) b = b.join(' ');
+  return a + ' ' + b;
+}
+
+function packageStyles(options) {
+  var styles = {};
+  if (options && (options.to || options.from)) {
+    styles.to = options.to;
+    styles.from = options.from;
+  }
+  return styles;
+}
+
+function pendClasses(classes, fix, isPrefix) {
+  var className = '';
+  classes = isArray(classes)
+      ? classes
+      : classes && isString(classes) && classes.length
+          ? classes.split(/\s+/)
+          : [];
+  forEach(classes, function(klass, i) {
+    if (klass && klass.length > 0) {
+      className += (i > 0) ? ' ' : '';
+      className += isPrefix ? fix + klass
+                            : klass + fix;
+    }
+  });
+  return className;
+}
+
+function removeFromArray(arr, val) {
+  var index = arr.indexOf(val);
+  if (val >= 0) {
+    arr.splice(index, 1);
+  }
+}
+
+function stripCommentsFromElement(element) {
+  if (element instanceof jqLite) {
+    switch (element.length) {
+      case 0:
+        return [];
+        break;
+
+      case 1:
+        // there is no point of stripping anything if the element
+        // is the only element within the jqLite wrapper.
+        // (it's important that we retain the element instance.)
+        if (element[0].nodeType === ELEMENT_NODE) {
+          return element;
+        }
+        break;
+
+      default:
+        return jqLite(extractElementNode(element));
+        break;
+    }
+  }
+
+  if (element.nodeType === ELEMENT_NODE) {
+    return jqLite(element);
+  }
+}
+
+function extractElementNode(element) {
+  if (!element[0]) return element;
+  for (var i = 0; i < element.length; i++) {
+    var elm = element[i];
+    if (elm.nodeType == ELEMENT_NODE) {
+      return elm;
+    }
+  }
+}
+
+function $$addClass($$jqLite, element, className) {
+  forEach(element, function(elm) {
+    $$jqLite.addClass(elm, className);
+  });
+}
+
+function $$removeClass($$jqLite, element, className) {
+  forEach(element, function(elm) {
+    $$jqLite.removeClass(elm, className);
+  });
+}
+
+function applyAnimationClassesFactory($$jqLite) {
+  return function(element, options) {
+    if (options.addClass) {
+      $$addClass($$jqLite, element, options.addClass);
+      options.addClass = null;
+    }
+    if (options.removeClass) {
+      $$removeClass($$jqLite, element, options.removeClass);
+      options.removeClass = null;
+    }
+  }
+}
+
+function prepareAnimationOptions(options) {
+  options = options || {};
+  if (!options.$$prepared) {
+    var domOperation = options.domOperation || noop;
+    options.domOperation = function() {
+      options.$$domOperationFired = true;
+      domOperation();
+      domOperation = noop;
+    };
+    options.$$prepared = true;
+  }
+  return options;
+}
+
+function applyAnimationStyles(element, options) {
+  applyAnimationFromStyles(element, options);
+  applyAnimationToStyles(element, options);
+}
+
+function applyAnimationFromStyles(element, options) {
+  if (options.from) {
+    element.css(options.from);
+    options.from = null;
+  }
+}
+
+function applyAnimationToStyles(element, options) {
+  if (options.to) {
+    element.css(options.to);
+    options.to = null;
+  }
+}
+
+function mergeAnimationDetails(element, oldAnimation, newAnimation) {
+  var target = oldAnimation.options || {};
+  var newOptions = newAnimation.options || {};
+
+  var toAdd = (target.addClass || '') + ' ' + (newOptions.addClass || '');
+  var toRemove = (target.removeClass || '') + ' ' + (newOptions.removeClass || '');
+  var classes = resolveElementClasses(element.attr('class'), toAdd, toRemove);
+
+  if (newOptions.preparationClasses) {
+    target.preparationClasses = concatWithSpace(newOptions.preparationClasses, target.preparationClasses);
+    delete newOptions.preparationClasses;
+  }
+
+  // noop is basically when there is no callback; otherwise something has been set
+  var realDomOperation = target.domOperation !== noop ? target.domOperation : null;
+
+  extend(target, newOptions);
+
+  // TODO(matsko or sreeramu): proper fix is to maintain all animation callback in array and call at last,but now only leave has the callback so no issue with this.
+  if (realDomOperation) {
+    target.domOperation = realDomOperation;
+  }
+
+  if (classes.addClass) {
+    target.addClass = classes.addClass;
+  } else {
+    target.addClass = null;
+  }
+
+  if (classes.removeClass) {
+    target.removeClass = classes.removeClass;
+  } else {
+    target.removeClass = null;
+  }
+
+  oldAnimation.addClass = target.addClass;
+  oldAnimation.removeClass = target.removeClass;
+
+  return target;
+}
+
+function resolveElementClasses(existing, toAdd, toRemove) {
+  var ADD_CLASS = 1;
+  var REMOVE_CLASS = -1;
+
+  var flags = {};
+  existing = splitClassesToLookup(existing);
+
+  toAdd = splitClassesToLookup(toAdd);
+  forEach(toAdd, function(value, key) {
+    flags[key] = ADD_CLASS;
+  });
+
+  toRemove = splitClassesToLookup(toRemove);
+  forEach(toRemove, function(value, key) {
+    flags[key] = flags[key] === ADD_CLASS ? null : REMOVE_CLASS;
+  });
+
+  var classes = {
+    addClass: '',
+    removeClass: ''
+  };
+
+  forEach(flags, function(val, klass) {
+    var prop, allow;
+    if (val === ADD_CLASS) {
+      prop = 'addClass';
+      allow = !existing[klass];
+    } else if (val === REMOVE_CLASS) {
+      prop = 'removeClass';
+      allow = existing[klass];
+    }
+    if (allow) {
+      if (classes[prop].length) {
+        classes[prop] += ' ';
+      }
+      classes[prop] += klass;
+    }
+  });
+
+  function splitClassesToLookup(classes) {
+    if (isString(classes)) {
+      classes = classes.split(' ');
+    }
+
+    var obj = {};
+    forEach(classes, function(klass) {
+      // sometimes the split leaves empty string values
+      // incase extra spaces were applied to the options
+      if (klass.length) {
+        obj[klass] = true;
+      }
+    });
+    return obj;
+  }
+
+  return classes;
+}
+
+function getDomNode(element) {
+  return (element instanceof angular.element) ? element[0] : element;
+}
+
+function applyGeneratedPreparationClasses(element, event, options) {
+  var classes = '';
+  if (event) {
+    classes = pendClasses(event, EVENT_CLASS_PREFIX, true);
+  }
+  if (options.addClass) {
+    classes = concatWithSpace(classes, pendClasses(options.addClass, ADD_CLASS_SUFFIX));
+  }
+  if (options.removeClass) {
+    classes = concatWithSpace(classes, pendClasses(options.removeClass, REMOVE_CLASS_SUFFIX));
+  }
+  if (classes.length) {
+    options.preparationClasses = classes;
+    element.addClass(classes);
+  }
+}
+
+function clearGeneratedClasses(element, options) {
+  if (options.preparationClasses) {
+    element.removeClass(options.preparationClasses);
+    options.preparationClasses = null;
+  }
+  if (options.activeClasses) {
+    element.removeClass(options.activeClasses);
+    options.activeClasses = null;
+  }
+}
+
+function blockTransitions(node, duration) {
+  // we use a negative delay value since it performs blocking
+  // yet it doesn't kill any existing transitions running on the
+  // same element which makes this safe for class-based animations
+  var value = duration ? '-' + duration + 's' : '';
+  applyInlineStyle(node, [TRANSITION_DELAY_PROP, value]);
+  return [TRANSITION_DELAY_PROP, value];
+}
+
+function blockKeyframeAnimations(node, applyBlock) {
+  var value = applyBlock ? 'paused' : '';
+  var key = ANIMATION_PROP + ANIMATION_PLAYSTATE_KEY;
+  applyInlineStyle(node, [key, value]);
+  return [key, value];
+}
+
+function applyInlineStyle(node, styleTuple) {
+  var prop = styleTuple[0];
+  var value = styleTuple[1];
+  node.style[prop] = value;
+}
+
+function concatWithSpace(a,b) {
+  if (!a) return b;
+  if (!b) return a;
+  return a + ' ' + b;
+}
+
+var $$rAFSchedulerFactory = ['$$rAF', function($$rAF) {
+  var queue, cancelFn;
+
+  function scheduler(tasks) {
+    // we make a copy since RAFScheduler mutates the state
+    // of the passed in array variable and this would be difficult
+    // to track down on the outside code
+    queue = queue.concat(tasks);
+    nextTick();
+  }
+
+  queue = scheduler.queue = [];
+
+  /* waitUntilQuiet does two things:
+   * 1. It will run the FINAL `fn` value only when an uncanceled RAF has passed through
+   * 2. It will delay the next wave of tasks from running until the quiet `fn` has run.
+   *
+   * The motivation here is that animation code can request more time from the scheduler
+   * before the next wave runs. This allows for certain DOM properties such as classes to
+   * be resolved in time for the next animation to run.
+   */
+  scheduler.waitUntilQuiet = function(fn) {
+    if (cancelFn) cancelFn();
+
+    cancelFn = $$rAF(function() {
+      cancelFn = null;
+      fn();
+      nextTick();
+    });
+  };
+
+  return scheduler;
+
+  function nextTick() {
+    if (!queue.length) return;
+
+    var items = queue.shift();
+    for (var i = 0; i < items.length; i++) {
+      items[i]();
+    }
+
+    if (!cancelFn) {
+      $$rAF(function() {
+        if (!cancelFn) nextTick();
+      });
+    }
+  }
+}];
+
+/**
+ * @ngdoc directive
+ * @name ngAnimateChildren
+ * @restrict AE
+ * @element ANY
+ *
+ * @description
+ *
+ * ngAnimateChildren allows you to specify that children of this element should animate even if any
+ * of the children's parents are currently animating. By default, when an element has an active `enter`, `leave`, or `move`
+ * (structural) animation, child elements that also have an active structural animation are not animated.
+ *
+ * Note that even if `ngAnimteChildren` is set, no child animations will run when the parent element is removed from the DOM (`leave` animation).
+ *
+ *
+ * @param {string} ngAnimateChildren If the value is empty, `true` or `on`,
+ *     then child animations are allowed. If the value is `false`, child animations are not allowed.
+ *
+ * @example
+ * <example module="ngAnimateChildren" name="ngAnimateChildren" deps="angular-animate.js" animations="true">
+     <file name="index.html">
+       <div ng-controller="mainController as main">
+         <label>Show container? <input type="checkbox" ng-model="main.enterElement" /></label>
+         <label>Animate children? <input type="checkbox" ng-model="main.animateChildren" /></label>
+         <hr>
+         <div ng-animate-children="{{main.animateChildren}}">
+           <div ng-if="main.enterElement" class="container">
+             List of items:
+             <div ng-repeat="item in [0, 1, 2, 3]" class="item">Item {{item}}</div>
+           </div>
+         </div>
+       </div>
+     </file>
+     <file name="animations.css">
+
+      .container.ng-enter,
+      .container.ng-leave {
+        transition: all ease 1.5s;
+      }
+
+      .container.ng-enter,
+      .container.ng-leave-active {
+        opacity: 0;
+      }
+
+      .container.ng-leave,
+      .container.ng-enter-active {
+        opacity: 1;
+      }
+
+      .item {
+        background: firebrick;
+        color: #FFF;
+        margin-bottom: 10px;
+      }
+
+      .item.ng-enter,
+      .item.ng-leave {
+        transition: transform 1.5s ease;
+      }
+
+      .item.ng-enter {
+        transform: translateX(50px);
+      }
+
+      .item.ng-enter-active {
+        transform: translateX(0);
+      }
+    </file>
+    <file name="script.js">
+      angular.module('ngAnimateChildren', ['ngAnimate'])
+        .controller('mainController', function() {
+          this.animateChildren = false;
+          this.enterElement = false;
+        });
+    </file>
+  </example>
+ */
+var $$AnimateChildrenDirective = ['$interpolate', function($interpolate) {
+  return {
+    link: function(scope, element, attrs) {
+      var val = attrs.ngAnimateChildren;
+      if (angular.isString(val) && val.length === 0) { //empty attribute
+        element.data(NG_ANIMATE_CHILDREN_DATA, true);
+      } else {
+        // Interpolate and set the value, so that it is available to
+        // animations that run right after compilation
+        setData($interpolate(val)(scope));
+        attrs.$observe('ngAnimateChildren', setData);
+      }
+
+      function setData(value) {
+        value = value === 'on' || value === 'true';
+        element.data(NG_ANIMATE_CHILDREN_DATA, value);
+      }
+    }
+  };
+}];
+
+var ANIMATE_TIMER_KEY = '$$animateCss';
+
+/**
+ * @ngdoc service
+ * @name $animateCss
+ * @kind object
+ *
+ * @description
+ * The `$animateCss` service is a useful utility to trigger customized CSS-based transitions/keyframes
+ * from a JavaScript-based animation or directly from a directive. The purpose of `$animateCss` is NOT
+ * to side-step how `$animate` and ngAnimate work, but the goal is to allow pre-existing animations or
+ * directives to create more complex animations that can be purely driven using CSS code.
+ *
+ * Note that only browsers that support CSS transitions and/or keyframe animations are capable of
+ * rendering animations triggered via `$animateCss` (bad news for IE9 and lower).
+ *
+ * ## Usage
+ * Once again, `$animateCss` is designed to be used inside of a registered JavaScript animation that
+ * is powered by ngAnimate. It is possible to use `$animateCss` directly inside of a directive, however,
+ * any automatic control over cancelling animations and/or preventing animations from being run on
+ * child elements will not be handled by Angular. For this to work as expected, please use `$animate` to
+ * trigger the animation and then setup a JavaScript animation that injects `$animateCss` to trigger
+ * the CSS animation.
+ *
+ * The example below shows how we can create a folding animation on an element using `ng-if`:
+ *
+ * ```html
+ * <!-- notice the `fold-animation` CSS class -->
+ * <div ng-if="onOff" class="fold-animation">
+ *   This element will go BOOM
+ * </div>
+ * <button ng-click="onOff=true">Fold In</button>
+ * ```
+ *
+ * Now we create the **JavaScript animation** that will trigger the CSS transition:
+ *
+ * ```js
+ * ngModule.animation('.fold-animation', ['$animateCss', function($animateCss) {
+ *   return {
+ *     enter: function(element, doneFn) {
+ *       var height = element[0].offsetHeight;
+ *       return $animateCss(element, {
+ *         from: { height:'0px' },
+ *         to: { height:height + 'px' },
+ *         duration: 1 // one second
+ *       });
+ *     }
+ *   }
+ * }]);
+ * ```
+ *
+ * ## More Advanced Uses
+ *
+ * `$animateCss` is the underlying code that ngAnimate uses to power **CSS-based animations** behind the scenes. Therefore CSS hooks
+ * like `.ng-EVENT`, `.ng-EVENT-active`, `.ng-EVENT-stagger` are all features that can be triggered using `$animateCss` via JavaScript code.
+ *
+ * This also means that just about any combination of adding classes, removing classes, setting styles, dynamically setting a keyframe animation,
+ * applying a hardcoded duration or delay value, changing the animation easing or applying a stagger animation are all options that work with
+ * `$animateCss`. The service itself is smart enough to figure out the combination of options and examine the element styling properties in order
+ * to provide a working animation that will run in CSS.
+ *
+ * The example below showcases a more advanced version of the `.fold-animation` from the example above:
+ *
+ * ```js
+ * ngModule.animation('.fold-animation', ['$animateCss', function($animateCss) {
+ *   return {
+ *     enter: function(element, doneFn) {
+ *       var height = element[0].offsetHeight;
+ *       return $animateCss(element, {
+ *         addClass: 'red large-text pulse-twice',
+ *         easing: 'ease-out',
+ *         from: { height:'0px' },
+ *         to: { height:height + 'px' },
+ *         duration: 1 // one second
+ *       });
+ *     }
+ *   }
+ * }]);
+ * ```
+ *
+ * Since we're adding/removing CSS classes then the CSS transition will also pick those up:
+ *
+ * ```css
+ * /&#42; since a hardcoded duration value of 1 was provided in the JavaScript animation code,
+ * the CSS classes below will be transitioned despite them being defined as regular CSS classes &#42;/
+ * .red { background:red; }
+ * .large-text { font-size:20px; }
+ *
+ * /&#42; we can also use a keyframe animation and $animateCss will make it work alongside the transition &#42;/
+ * .pulse-twice {
+ *   animation: 0.5s pulse linear 2;
+ *   -webkit-animation: 0.5s pulse linear 2;
+ * }
+ *
+ * @keyframes pulse {
+ *   from { transform: scale(0.5); }
+ *   to { transform: scale(1.5); }
+ * }
+ *
+ * @-webkit-keyframes pulse {
+ *   from { -webkit-transform: scale(0.5); }
+ *   to { -webkit-transform: scale(1.5); }
+ * }
+ * ```
+ *
+ * Given this complex combination of CSS classes, styles and options, `$animateCss` will figure everything out and make the animation happen.
+ *
+ * ## How the Options are handled
+ *
+ * `$animateCss` is very versatile and intelligent when it comes to figuring out what configurations to apply to the element to ensure the animation
+ * works with the options provided. Say for example we were adding a class that contained a keyframe value and we wanted to also animate some inline
+ * styles using the `from` and `to` properties.
+ *
+ * ```js
+ * var animator = $animateCss(element, {
+ *   from: { background:'red' },
+ *   to: { background:'blue' }
+ * });
+ * animator.start();
+ * ```
+ *
+ * ```css
+ * .rotating-animation {
+ *   animation:0.5s rotate linear;
+ *   -webkit-animation:0.5s rotate linear;
+ * }
+ *
+ * @keyframes rotate {
+ *   from { transform: rotate(0deg); }
+ *   to { transform: rotate(360deg); }
+ * }
+ *
+ * @-webkit-keyframes rotate {
+ *   from { -webkit-transform: rotate(0deg); }
+ *   to { -webkit-transform: rotate(360deg); }
+ * }
+ * ```
+ *
+ * The missing pieces here are that we do not have a transition set (within the CSS code nor within the `$animateCss` options) and the duration of the animation is
+ * going to be detected from what the keyframe styles on the CSS class are. In this event, `$animateCss` will automatically create an inline transition
+ * style matching the duration detected from the keyframe style (which is present in the CSS class that is being added) and then prepare both the transition
+ * and keyframe animations to run in parallel on the element. Then when the animation is underway the provided `from` and `to` CSS styles will be applied
+ * and spread across the transition and keyframe animation.
+ *
+ * ## What is returned
+ *
+ * `$animateCss` works in two stages: a preparation phase and an animation phase. Therefore when `$animateCss` is first called it will NOT actually
+ * start the animation. All that is going on here is that the element is being prepared for the animation (which means that the generated CSS classes are
+ * added and removed on the element). Once `$animateCss` is called it will return an object with the following properties:
+ *
+ * ```js
+ * var animator = $animateCss(element, { ... });
+ * ```
+ *
+ * Now what do the contents of our `animator` variable look like:
+ *
+ * ```js
+ * {
+ *   // starts the animation
+ *   start: Function,
+ *
+ *   // ends (aborts) the animation
+ *   end: Function
+ * }
+ * ```
+ *
+ * To actually start the animation we need to run `animation.start()` which will then return a promise that we can hook into to detect when the animation ends.
+ * If we choose not to run the animation then we MUST run `animation.end()` to perform a cleanup on the element (since some CSS classes and styles may have been
+ * applied to the element during the preparation phase). Note that all other properties such as duration, delay, transitions and keyframes are just properties
+ * and that changing them will not reconfigure the parameters of the animation.
+ *
+ * ### runner.done() vs runner.then()
+ * It is documented that `animation.start()` will return a promise object and this is true, however, there is also an additional method available on the
+ * runner called `.done(callbackFn)`. The done method works the same as `.finally(callbackFn)`, however, it does **not trigger a digest to occur**.
+ * Therefore, for performance reasons, it's always best to use `runner.done(callback)` instead of `runner.then()`, `runner.catch()` or `runner.finally()`
+ * unless you really need a digest to kick off afterwards.
+ *
+ * Keep in mind that, to make this easier, ngAnimate has tweaked the JS animations API to recognize when a runner instance is returned from $animateCss
+ * (so there is no need to call `runner.done(doneFn)` inside of your JavaScript animation code).
+ * Check the {@link ngAnimate.$animateCss#usage animation code above} to see how this works.
+ *
+ * @param {DOMElement} element the element that will be animated
+ * @param {object} options the animation-related options that will be applied during the animation
+ *
+ * * `event` - The DOM event (e.g. enter, leave, move). When used, a generated CSS class of `ng-EVENT` and `ng-EVENT-active` will be applied
+ * to the element during the animation. Multiple events can be provided when spaces are used as a separator. (Note that this will not perform any DOM operation.)
+ * * `structural` - Indicates that the `ng-` prefix will be added to the event class. Setting to `false` or omitting will turn `ng-EVENT` and
+ * `ng-EVENT-active` in `EVENT` and `EVENT-active`. Unused if `event` is omitted.
+ * * `easing` - The CSS easing value that will be applied to the transition or keyframe animation (or both).
+ * * `transitionStyle` - The raw CSS transition style that will be used (e.g. `1s linear all`).
+ * * `keyframeStyle` - The raw CSS keyframe animation style that will be used (e.g. `1s my_animation linear`).
+ * * `from` - The starting CSS styles (a key/value object) that will be applied at the start of the animation.
+ * * `to` - The ending CSS styles (a key/value object) that will be applied across the animation via a CSS transition.
+ * * `addClass` - A space separated list of CSS classes that will be added to the element and spread across the animation.
+ * * `removeClass` - A space separated list of CSS classes that will be removed from the element and spread across the animation.
+ * * `duration` - A number value representing the total duration of the transition and/or keyframe (note that a value of 1 is 1000ms). If a value of `0`
+ * is provided then the animation will be skipped entirely.
+ * * `delay` - A number value representing the total delay of the transition and/or keyframe (note that a value of 1 is 1000ms). If a value of `true` is
+ * used then whatever delay value is detected from the CSS classes will be mirrored on the elements styles (e.g. by setting delay true then the style value
+ * of the element will be `transition-delay: DETECTED_VALUE`). Using `true` is useful when you want the CSS classes and inline styles to all share the same
+ * CSS delay value.
+ * * `stagger` - A numeric time value representing the delay between successively animated elements
+ * ({@link ngAnimate#css-staggering-animations Click here to learn how CSS-based staggering works in ngAnimate.})
+ * * `staggerIndex` - The numeric index representing the stagger item (e.g. a value of 5 is equal to the sixth item in the stagger; therefore when a
+ *   `stagger` option value of `0.1` is used then there will be a stagger delay of `600ms`)
+ * * `applyClassesEarly` - Whether or not the classes being added or removed will be used when detecting the animation. This is set by `$animate` when enter/leave/move animations are fired to ensure that the CSS classes are resolved in time. (Note that this will prevent any transitions from occurring on the classes being added and removed.)
+ * * `cleanupStyles` - Whether or not the provided `from` and `to` styles will be removed once
+ *    the animation is closed. This is useful for when the styles are used purely for the sake of
+ *    the animation and do not have a lasting visual effect on the element (e.g. a collapse and open animation).
+ *    By default this value is set to `false`.
+ *
+ * @return {object} an object with start and end methods and details about the animation.
+ *
+ * * `start` - The method to start the animation. This will return a `Promise` when called.
+ * * `end` - This method will cancel the animation and remove all applied CSS classes and styles.
+ */
+var ONE_SECOND = 1000;
+var BASE_TEN = 10;
+
+var ELAPSED_TIME_MAX_DECIMAL_PLACES = 3;
+var CLOSING_TIME_BUFFER = 1.5;
+
+var DETECT_CSS_PROPERTIES = {
+  transitionDuration:      TRANSITION_DURATION_PROP,
+  transitionDelay:         TRANSITION_DELAY_PROP,
+  transitionProperty:      TRANSITION_PROP + PROPERTY_KEY,
+  animationDuration:       ANIMATION_DURATION_PROP,
+  animationDelay:          ANIMATION_DELAY_PROP,
+  animationIterationCount: ANIMATION_PROP + ANIMATION_ITERATION_COUNT_KEY
+};
+
+var DETECT_STAGGER_CSS_PROPERTIES = {
+  transitionDuration:      TRANSITION_DURATION_PROP,
+  transitionDelay:         TRANSITION_DELAY_PROP,
+  animationDuration:       ANIMATION_DURATION_PROP,
+  animationDelay:          ANIMATION_DELAY_PROP
+};
+
+function getCssKeyframeDurationStyle(duration) {
+  return [ANIMATION_DURATION_PROP, duration + 's'];
+}
+
+function getCssDelayStyle(delay, isKeyframeAnimation) {
+  var prop = isKeyframeAnimation ? ANIMATION_DELAY_PROP : TRANSITION_DELAY_PROP;
+  return [prop, delay + 's'];
+}
+
+function computeCssStyles($window, element, properties) {
+  var styles = Object.create(null);
+  var detectedStyles = $window.getComputedStyle(element) || {};
+  forEach(properties, function(formalStyleName, actualStyleName) {
+    var val = detectedStyles[formalStyleName];
+    if (val) {
+      var c = val.charAt(0);
+
+      // only numerical-based values have a negative sign or digit as the first value
+      if (c === '-' || c === '+' || c >= 0) {
+        val = parseMaxTime(val);
+      }
+
+      // by setting this to null in the event that the delay is not set or is set directly as 0
+      // then we can still allow for negative values to be used later on and not mistake this
+      // value for being greater than any other negative value.
+      if (val === 0) {
+        val = null;
+      }
+      styles[actualStyleName] = val;
+    }
+  });
+
+  return styles;
+}
+
+function parseMaxTime(str) {
+  var maxValue = 0;
+  var values = str.split(/\s*,\s*/);
+  forEach(values, function(value) {
+    // it's always safe to consider only second values and omit `ms` values since
+    // getComputedStyle will always handle the conversion for us
+    if (value.charAt(value.length - 1) == 's') {
+      value = value.substring(0, value.length - 1);
+    }
+    value = parseFloat(value) || 0;
+    maxValue = maxValue ? Math.max(value, maxValue) : value;
+  });
+  return maxValue;
+}
+
+function truthyTimingValue(val) {
+  return val === 0 || val != null;
+}
+
+function getCssTransitionDurationStyle(duration, applyOnlyDuration) {
+  var style = TRANSITION_PROP;
+  var value = duration + 's';
+  if (applyOnlyDuration) {
+    style += DURATION_KEY;
+  } else {
+    value += ' linear all';
+  }
+  return [style, value];
+}
+
+function createLocalCacheLookup() {
+  var cache = Object.create(null);
+  return {
+    flush: function() {
+      cache = Object.create(null);
+    },
+
+    count: function(key) {
+      var entry = cache[key];
+      return entry ? entry.total : 0;
+    },
+
+    get: function(key) {
+      var entry = cache[key];
+      return entry && entry.value;
+    },
+
+    put: function(key, value) {
+      if (!cache[key]) {
+        cache[key] = { total: 1, value: value };
+      } else {
+        cache[key].total++;
+      }
+    }
+  };
+}
+
+// we do not reassign an already present style value since
+// if we detect the style property value again we may be
+// detecting styles that were added via the `from` styles.
+// We make use of `isDefined` here since an empty string
+// or null value (which is what getPropertyValue will return
+// for a non-existing style) will still be marked as a valid
+// value for the style (a falsy value implies that the style
+// is to be removed at the end of the animation). If we had a simple
+// "OR" statement then it would not be enough to catch that.
+function registerRestorableStyles(backup, node, properties) {
+  forEach(properties, function(prop) {
+    backup[prop] = isDefined(backup[prop])
+        ? backup[prop]
+        : node.style.getPropertyValue(prop);
+  });
+}
+
+var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
+  var gcsLookup = createLocalCacheLookup();
+  var gcsStaggerLookup = createLocalCacheLookup();
+
+  this.$get = ['$window', '$$jqLite', '$$AnimateRunner', '$timeout',
+               '$$forceReflow', '$sniffer', '$$rAFScheduler', '$$animateQueue',
+       function($window,   $$jqLite,   $$AnimateRunner,   $timeout,
+                $$forceReflow,   $sniffer,   $$rAFScheduler, $$animateQueue) {
+
+    var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
+
+    var parentCounter = 0;
+    function gcsHashFn(node, extraClasses) {
+      var KEY = "$$ngAnimateParentKey";
+      var parentNode = node.parentNode;
+      var parentID = parentNode[KEY] || (parentNode[KEY] = ++parentCounter);
+      return parentID + '-' + node.getAttribute('class') + '-' + extraClasses;
+    }
+
+    function computeCachedCssStyles(node, className, cacheKey, properties) {
+      var timings = gcsLookup.get(cacheKey);
+
+      if (!timings) {
+        timings = computeCssStyles($window, node, properties);
+        if (timings.animationIterationCount === 'infinite') {
+          timings.animationIterationCount = 1;
+        }
+      }
+
+      // we keep putting this in multiple times even though the value and the cacheKey are the same
+      // because we're keeping an internal tally of how many duplicate animations are detected.
+      gcsLookup.put(cacheKey, timings);
+      return timings;
+    }
+
+    function computeCachedCssStaggerStyles(node, className, cacheKey, properties) {
+      var stagger;
+
+      // if we have one or more existing matches of matching elements
+      // containing the same parent + CSS styles (which is how cacheKey works)
+      // then staggering is possible
+      if (gcsLookup.count(cacheKey) > 0) {
+        stagger = gcsStaggerLookup.get(cacheKey);
+
+        if (!stagger) {
+          var staggerClassName = pendClasses(className, '-stagger');
+
+          $$jqLite.addClass(node, staggerClassName);
+
+          stagger = computeCssStyles($window, node, properties);
+
+          // force the conversion of a null value to zero incase not set
+          stagger.animationDuration = Math.max(stagger.animationDuration, 0);
+          stagger.transitionDuration = Math.max(stagger.transitionDuration, 0);
+
+          $$jqLite.removeClass(node, staggerClassName);
+
+          gcsStaggerLookup.put(cacheKey, stagger);
+        }
+      }
+
+      return stagger || {};
+    }
+
+    var cancelLastRAFRequest;
+    var rafWaitQueue = [];
+    function waitUntilQuiet(callback) {
+      rafWaitQueue.push(callback);
+      $$rAFScheduler.waitUntilQuiet(function() {
+        gcsLookup.flush();
+        gcsStaggerLookup.flush();
+
+        // DO NOT REMOVE THIS LINE OR REFACTOR OUT THE `pageWidth` variable.
+        // PLEASE EXAMINE THE `$$forceReflow` service to understand why.
+        var pageWidth = $$forceReflow();
+
+        // we use a for loop to ensure that if the queue is changed
+        // during this looping then it will consider new requests
+        for (var i = 0; i < rafWaitQueue.length; i++) {
+          rafWaitQueue[i](pageWidth);
+        }
+        rafWaitQueue.length = 0;
+      });
+    }
+
+    function computeTimings(node, className, cacheKey) {
+      var timings = computeCachedCssStyles(node, className, cacheKey, DETECT_CSS_PROPERTIES);
+      var aD = timings.animationDelay;
+      var tD = timings.transitionDelay;
+      timings.maxDelay = aD && tD
+          ? Math.max(aD, tD)
+          : (aD || tD);
+      timings.maxDuration = Math.max(
+          timings.animationDuration * timings.animationIterationCount,
+          timings.transitionDuration);
+
+      return timings;
+    }
+
+    return function init(element, initialOptions) {
+      // all of the animation functions should create
+      // a copy of the options data, however, if a
+      // parent service has already created a copy then
+      // we should stick to using that
+      var options = initialOptions || {};
+      if (!options.$$prepared) {
+        options = prepareAnimationOptions(copy(options));
+      }
+
+      var restoreStyles = {};
+      var node = getDomNode(element);
+      if (!node
+          || !node.parentNode
+          || !$$animateQueue.enabled()) {
+        return closeAndReturnNoopAnimator();
+      }
+
+      var temporaryStyles = [];
+      var classes = element.attr('class');
+      var styles = packageStyles(options);
+      var animationClosed;
+      var animationPaused;
+      var animationCompleted;
+      var runner;
+      var runnerHost;
+      var maxDelay;
+      var maxDelayTime;
+      var maxDuration;
+      var maxDurationTime;
+      var startTime;
+      var events = [];
+
+      if (options.duration === 0 || (!$sniffer.animations && !$sniffer.transitions)) {
+        return closeAndReturnNoopAnimator();
+      }
+
+      var method = options.event && isArray(options.event)
+            ? options.event.join(' ')
+            : options.event;
+
+      var isStructural = method && options.structural;
+      var structuralClassName = '';
+      var addRemoveClassName = '';
+
+      if (isStructural) {
+        structuralClassName = pendClasses(method, EVENT_CLASS_PREFIX, true);
+      } else if (method) {
+        structuralClassName = method;
+      }
+
+      if (options.addClass) {
+        addRemoveClassName += pendClasses(options.addClass, ADD_CLASS_SUFFIX);
+      }
+
+      if (options.removeClass) {
+        if (addRemoveClassName.length) {
+          addRemoveClassName += ' ';
+        }
+        addRemoveClassName += pendClasses(options.removeClass, REMOVE_CLASS_SUFFIX);
+      }
+
+      // there may be a situation where a structural animation is combined together
+      // with CSS classes that need to resolve before the animation is computed.
+      // However this means that there is no explicit CSS code to block the animation
+      // from happening (by setting 0s none in the class name). If this is the case
+      // we need to apply the classes before the first rAF so we know to continue if
+      // there actually is a detected transition or keyframe animation
+      if (options.applyClassesEarly && addRemoveClassName.length) {
+        applyAnimationClasses(element, options);
+      }
+
+      var preparationClasses = [structuralClassName, addRemoveClassName].join(' ').trim();
+      var fullClassName = classes + ' ' + preparationClasses;
+      var activeClasses = pendClasses(preparationClasses, ACTIVE_CLASS_SUFFIX);
+      var hasToStyles = styles.to && Object.keys(styles.to).length > 0;
+      var containsKeyframeAnimation = (options.keyframeStyle || '').length > 0;
+
+      // there is no way we can trigger an animation if no styles and
+      // no classes are being applied which would then trigger a transition,
+      // unless there a is raw keyframe value that is applied to the element.
+      if (!containsKeyframeAnimation
+           && !hasToStyles
+           && !preparationClasses) {
+        return closeAndReturnNoopAnimator();
+      }
+
+      var cacheKey, stagger;
+      if (options.stagger > 0) {
+        var staggerVal = parseFloat(options.stagger);
+        stagger = {
+          transitionDelay: staggerVal,
+          animationDelay: staggerVal,
+          transitionDuration: 0,
+          animationDuration: 0
+        };
+      } else {
+        cacheKey = gcsHashFn(node, fullClassName);
+        stagger = computeCachedCssStaggerStyles(node, preparationClasses, cacheKey, DETECT_STAGGER_CSS_PROPERTIES);
+      }
+
+      if (!options.$$skipPreparationClasses) {
+        $$jqLite.addClass(element, preparationClasses);
+      }
+
+      var applyOnlyDuration;
+
+      if (options.transitionStyle) {
+        var transitionStyle = [TRANSITION_PROP, options.transitionStyle];
+        applyInlineStyle(node, transitionStyle);
+        temporaryStyles.push(transitionStyle);
+      }
+
+      if (options.duration >= 0) {
+        applyOnlyDuration = node.style[TRANSITION_PROP].length > 0;
+        var durationStyle = getCssTransitionDurationStyle(options.duration, applyOnlyDuration);
+
+        // we set the duration so that it will be picked up by getComputedStyle later
+        applyInlineStyle(node, durationStyle);
+        temporaryStyles.push(durationStyle);
+      }
+
+      if (options.keyframeStyle) {
+        var keyframeStyle = [ANIMATION_PROP, options.keyframeStyle];
+        applyInlineStyle(node, keyframeStyle);
+        temporaryStyles.push(keyframeStyle);
+      }
+
+      var itemIndex = stagger
+          ? options.staggerIndex >= 0
+              ? options.staggerIndex
+              : gcsLookup.count(cacheKey)
+          : 0;
+
+      var isFirst = itemIndex === 0;
+
+      // this is a pre-emptive way of forcing the setup classes to be added and applied INSTANTLY
+      // without causing any combination of transitions to kick in. By adding a negative delay value
+      // it forces the setup class' transition to end immediately. We later then remove the negative
+      // transition delay to allow for the transition to naturally do it's thing. The beauty here is
+      // that if there is no transition defined then nothing will happen and this will also allow
+      // other transitions to be stacked on top of each other without any chopping them out.
+      if (isFirst && !options.skipBlocking) {
+        blockTransitions(node, SAFE_FAST_FORWARD_DURATION_VALUE);
+      }
+
+      var timings = computeTimings(node, fullClassName, cacheKey);
+      var relativeDelay = timings.maxDelay;
+      maxDelay = Math.max(relativeDelay, 0);
+      maxDuration = timings.maxDuration;
+
+      var flags = {};
+      flags.hasTransitions          = timings.transitionDuration > 0;
+      flags.hasAnimations           = timings.animationDuration > 0;
+      flags.hasTransitionAll        = flags.hasTransitions && timings.transitionProperty == 'all';
+      flags.applyTransitionDuration = hasToStyles && (
+                                        (flags.hasTransitions && !flags.hasTransitionAll)
+                                         || (flags.hasAnimations && !flags.hasTransitions));
+      flags.applyAnimationDuration  = options.duration && flags.hasAnimations;
+      flags.applyTransitionDelay    = truthyTimingValue(options.delay) && (flags.applyTransitionDuration || flags.hasTransitions);
+      flags.applyAnimationDelay     = truthyTimingValue(options.delay) && flags.hasAnimations;
+      flags.recalculateTimingStyles = addRemoveClassName.length > 0;
+
+      if (flags.applyTransitionDuration || flags.applyAnimationDuration) {
+        maxDuration = options.duration ? parseFloat(options.duration) : maxDuration;
+
+        if (flags.applyTransitionDuration) {
+          flags.hasTransitions = true;
+          timings.transitionDuration = maxDuration;
+          applyOnlyDuration = node.style[TRANSITION_PROP + PROPERTY_KEY].length > 0;
+          temporaryStyles.push(getCssTransitionDurationStyle(maxDuration, applyOnlyDuration));
+        }
+
+        if (flags.applyAnimationDuration) {
+          flags.hasAnimations = true;
+          timings.animationDuration = maxDuration;
+          temporaryStyles.push(getCssKeyframeDurationStyle(maxDuration));
+        }
+      }
+
+      if (maxDuration === 0 && !flags.recalculateTimingStyles) {
+        return closeAndReturnNoopAnimator();
+      }
+
+      if (options.delay != null) {
+        var delayStyle;
+        if (typeof options.delay !== "boolean") {
+          delayStyle = parseFloat(options.delay);
+          // number in options.delay means we have to recalculate the delay for the closing timeout
+          maxDelay = Math.max(delayStyle, 0);
+        }
+
+        if (flags.applyTransitionDelay) {
+          temporaryStyles.push(getCssDelayStyle(delayStyle));
+        }
+
+        if (flags.applyAnimationDelay) {
+          temporaryStyles.push(getCssDelayStyle(delayStyle, true));
+        }
+      }
+
+      // we need to recalculate the delay value since we used a pre-emptive negative
+      // delay value and the delay value is required for the final event checking. This
+      // property will ensure that this will happen after the RAF phase has passed.
+      if (options.duration == null && timings.transitionDuration > 0) {
+        flags.recalculateTimingStyles = flags.recalculateTimingStyles || isFirst;
+      }
+
+      maxDelayTime = maxDelay * ONE_SECOND;
+      maxDurationTime = maxDuration * ONE_SECOND;
+      if (!options.skipBlocking) {
+        flags.blockTransition = timings.transitionDuration > 0;
+        flags.blockKeyframeAnimation = timings.animationDuration > 0 &&
+                                       stagger.animationDelay > 0 &&
+                                       stagger.animationDuration === 0;
+      }
+
+      if (options.from) {
+        if (options.cleanupStyles) {
+          registerRestorableStyles(restoreStyles, node, Object.keys(options.from));
+        }
+        applyAnimationFromStyles(element, options);
+      }
+
+      if (flags.blockTransition || flags.blockKeyframeAnimation) {
+        applyBlocking(maxDuration);
+      } else if (!options.skipBlocking) {
+        blockTransitions(node, false);
+      }
+
+      // TODO(matsko): for 1.5 change this code to have an animator object for better debugging
+      return {
+        $$willAnimate: true,
+        end: endFn,
+        start: function() {
+          if (animationClosed) return;
+
+          runnerHost = {
+            end: endFn,
+            cancel: cancelFn,
+            resume: null, //this will be set during the start() phase
+            pause: null
+          };
+
+          runner = new $$AnimateRunner(runnerHost);
+
+          waitUntilQuiet(start);
+
+          // we don't have access to pause/resume the animation
+          // since it hasn't run yet. AnimateRunner will therefore
+          // set noop functions for resume and pause and they will
+          // later be overridden once the animation is triggered
+          return runner;
+        }
+      };
+
+      function endFn() {
+        close();
+      }
+
+      function cancelFn() {
+        close(true);
+      }
+
+      function close(rejected) { // jshint ignore:line
+        // if the promise has been called already then we shouldn't close
+        // the animation again
+        if (animationClosed || (animationCompleted && animationPaused)) return;
+        animationClosed = true;
+        animationPaused = false;
+
+        if (!options.$$skipPreparationClasses) {
+          $$jqLite.removeClass(element, preparationClasses);
+        }
+        $$jqLite.removeClass(element, activeClasses);
+
+        blockKeyframeAnimations(node, false);
+        blockTransitions(node, false);
+
+        forEach(temporaryStyles, function(entry) {
+          // There is only one way to remove inline style properties entirely from elements.
+          // By using `removeProperty` this works, but we need to convert camel-cased CSS
+          // styles down to hyphenated values.
+          node.style[entry[0]] = '';
+        });
+
+        applyAnimationClasses(element, options);
+        applyAnimationStyles(element, options);
+
+        if (Object.keys(restoreStyles).length) {
+          forEach(restoreStyles, function(value, prop) {
+            value ? node.style.setProperty(prop, value)
+                  : node.style.removeProperty(prop);
+          });
+        }
+
+        // the reason why we have this option is to allow a synchronous closing callback
+        // that is fired as SOON as the animation ends (when the CSS is removed) or if
+        // the animation never takes off at all. A good example is a leave animation since
+        // the element must be removed just after the animation is over or else the element
+        // will appear on screen for one animation frame causing an overbearing flicker.
+        if (options.onDone) {
+          options.onDone();
+        }
+
+        if (events && events.length) {
+          // Remove the transitionend / animationend listener(s)
+          element.off(events.join(' '), onAnimationProgress);
+        }
+
+        //Cancel the fallback closing timeout and remove the timer data
+        var animationTimerData = element.data(ANIMATE_TIMER_KEY);
+        if (animationTimerData) {
+          $timeout.cancel(animationTimerData[0].timer);
+          element.removeData(ANIMATE_TIMER_KEY);
+        }
+
+        // if the preparation function fails then the promise is not setup
+        if (runner) {
+          runner.complete(!rejected);
+        }
+      }
+
+      function applyBlocking(duration) {
+        if (flags.blockTransition) {
+          blockTransitions(node, duration);
+        }
+
+        if (flags.blockKeyframeAnimation) {
+          blockKeyframeAnimations(node, !!duration);
+        }
+      }
+
+      function closeAndReturnNoopAnimator() {
+        runner = new $$AnimateRunner({
+          end: endFn,
+          cancel: cancelFn
+        });
+
+        // should flush the cache animation
+        waitUntilQuiet(noop);
+        close();
+
+        return {
+          $$willAnimate: false,
+          start: function() {
+            return runner;
+          },
+          end: endFn
+        };
+      }
+
+      function onAnimationProgress(event) {
+        event.stopPropagation();
+        var ev = event.originalEvent || event;
+
+        // we now always use `Date.now()` due to the recent changes with
+        // event.timeStamp in Firefox, Webkit and Chrome (see #13494 for more info)
+        var timeStamp = ev.$manualTimeStamp || Date.now();
+
+        /* Firefox (or possibly just Gecko) likes to not round values up
+         * when a ms measurement is used for the animation */
+        var elapsedTime = parseFloat(ev.elapsedTime.toFixed(ELAPSED_TIME_MAX_DECIMAL_PLACES));
+
+        /* $manualTimeStamp is a mocked timeStamp value which is set
+         * within browserTrigger(). This is only here so that tests can
+         * mock animations properly. Real events fallback to event.timeStamp,
+         * or, if they don't, then a timeStamp is automatically created for them.
+         * We're checking to see if the timeStamp surpasses the expected delay,
+         * but we're using elapsedTime instead of the timeStamp on the 2nd
+         * pre-condition since animationPauseds sometimes close off early */
+        if (Math.max(timeStamp - startTime, 0) >= maxDelayTime && elapsedTime >= maxDuration) {
+          // we set this flag to ensure that if the transition is paused then, when resumed,
+          // the animation will automatically close itself since transitions cannot be paused.
+          animationCompleted = true;
+          close();
+        }
+      }
+
+      function start() {
+        if (animationClosed) return;
+        if (!node.parentNode) {
+          close();
+          return;
+        }
+
+        // even though we only pause keyframe animations here the pause flag
+        // will still happen when transitions are used. Only the transition will
+        // not be paused since that is not possible. If the animation ends when
+        // paused then it will not complete until unpaused or cancelled.
+        var playPause = function(playAnimation) {
+          if (!animationCompleted) {
+            animationPaused = !playAnimation;
+            if (timings.animationDuration) {
+              var value = blockKeyframeAnimations(node, animationPaused);
+              animationPaused
+                  ? temporaryStyles.push(value)
+                  : removeFromArray(temporaryStyles, value);
+            }
+          } else if (animationPaused && playAnimation) {
+            animationPaused = false;
+            close();
+          }
+        };
+
+        // checking the stagger duration prevents an accidentally cascade of the CSS delay style
+        // being inherited from the parent. If the transition duration is zero then we can safely
+        // rely that the delay value is an intentional stagger delay style.
+        var maxStagger = itemIndex > 0
+                         && ((timings.transitionDuration && stagger.transitionDuration === 0) ||
+                            (timings.animationDuration && stagger.animationDuration === 0))
+                         && Math.max(stagger.animationDelay, stagger.transitionDelay);
+        if (maxStagger) {
+          $timeout(triggerAnimationStart,
+                   Math.floor(maxStagger * itemIndex * ONE_SECOND),
+                   false);
+        } else {
+          triggerAnimationStart();
+        }
+
+        // this will decorate the existing promise runner with pause/resume methods
+        runnerHost.resume = function() {
+          playPause(true);
+        };
+
+        runnerHost.pause = function() {
+          playPause(false);
+        };
+
+        function triggerAnimationStart() {
+          // just incase a stagger animation kicks in when the animation
+          // itself was cancelled entirely
+          if (animationClosed) return;
+
+          applyBlocking(false);
+
+          forEach(temporaryStyles, function(entry) {
+            var key = entry[0];
+            var value = entry[1];
+            node.style[key] = value;
+          });
+
+          applyAnimationClasses(element, options);
+          $$jqLite.addClass(element, activeClasses);
+
+          if (flags.recalculateTimingStyles) {
+            fullClassName = node.className + ' ' + preparationClasses;
+            cacheKey = gcsHashFn(node, fullClassName);
+
+            timings = computeTimings(node, fullClassName, cacheKey);
+            relativeDelay = timings.maxDelay;
+            maxDelay = Math.max(relativeDelay, 0);
+            maxDuration = timings.maxDuration;
+
+            if (maxDuration === 0) {
+              close();
+              return;
+            }
+
+            flags.hasTransitions = timings.transitionDuration > 0;
+            flags.hasAnimations = timings.animationDuration > 0;
+          }
+
+          if (flags.applyAnimationDelay) {
+            relativeDelay = typeof options.delay !== "boolean" && truthyTimingValue(options.delay)
+                  ? parseFloat(options.delay)
+                  : relativeDelay;
+
+            maxDelay = Math.max(relativeDelay, 0);
+            timings.animationDelay = relativeDelay;
+            delayStyle = getCssDelayStyle(relativeDelay, true);
+            temporaryStyles.push(delayStyle);
+            node.style[delayStyle[0]] = delayStyle[1];
+          }
+
+          maxDelayTime = maxDelay * ONE_SECOND;
+          maxDurationTime = maxDuration * ONE_SECOND;
+
+          if (options.easing) {
+            var easeProp, easeVal = options.easing;
+            if (flags.hasTransitions) {
+              easeProp = TRANSITION_PROP + TIMING_KEY;
+              temporaryStyles.push([easeProp, easeVal]);
+              node.style[easeProp] = easeVal;
+            }
+            if (flags.hasAnimations) {
+              easeProp = ANIMATION_PROP + TIMING_KEY;
+              temporaryStyles.push([easeProp, easeVal]);
+              node.style[easeProp] = easeVal;
+            }
+          }
+
+          if (timings.transitionDuration) {
+            events.push(TRANSITIONEND_EVENT);
+          }
+
+          if (timings.animationDuration) {
+            events.push(ANIMATIONEND_EVENT);
+          }
+
+          startTime = Date.now();
+          var timerTime = maxDelayTime + CLOSING_TIME_BUFFER * maxDurationTime;
+          var endTime = startTime + timerTime;
+
+          var animationsData = element.data(ANIMATE_TIMER_KEY) || [];
+          var setupFallbackTimer = true;
+          if (animationsData.length) {
+            var currentTimerData = animationsData[0];
+            setupFallbackTimer = endTime > currentTimerData.expectedEndTime;
+            if (setupFallbackTimer) {
+              $timeout.cancel(currentTimerData.timer);
+            } else {
+              animationsData.push(close);
+            }
+          }
+
+          if (setupFallbackTimer) {
+            var timer = $timeout(onAnimationExpired, timerTime, false);
+            animationsData[0] = {
+              timer: timer,
+              expectedEndTime: endTime
+            };
+            animationsData.push(close);
+            element.data(ANIMATE_TIMER_KEY, animationsData);
+          }
+
+          if (events.length) {
+            element.on(events.join(' '), onAnimationProgress);
+          }
+
+          if (options.to) {
+            if (options.cleanupStyles) {
+              registerRestorableStyles(restoreStyles, node, Object.keys(options.to));
+            }
+            applyAnimationToStyles(element, options);
+          }
+        }
+
+        function onAnimationExpired() {
+          var animationsData = element.data(ANIMATE_TIMER_KEY);
+
+          // this will be false in the event that the element was
+          // removed from the DOM (via a leave animation or something
+          // similar)
+          if (animationsData) {
+            for (var i = 1; i < animationsData.length; i++) {
+              animationsData[i]();
+            }
+            element.removeData(ANIMATE_TIMER_KEY);
+          }
+        }
+      }
+    };
+  }];
+}];
+
+var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationProvider) {
+  $$animationProvider.drivers.push('$$animateCssDriver');
+
+  var NG_ANIMATE_SHIM_CLASS_NAME = 'ng-animate-shim';
+  var NG_ANIMATE_ANCHOR_CLASS_NAME = 'ng-anchor';
+
+  var NG_OUT_ANCHOR_CLASS_NAME = 'ng-anchor-out';
+  var NG_IN_ANCHOR_CLASS_NAME = 'ng-anchor-in';
+
+  function isDocumentFragment(node) {
+    return node.parentNode && node.parentNode.nodeType === 11;
+  }
+
+  this.$get = ['$animateCss', '$rootScope', '$$AnimateRunner', '$rootElement', '$sniffer', '$$jqLite', '$document',
+       function($animateCss,   $rootScope,   $$AnimateRunner,   $rootElement,   $sniffer,   $$jqLite,   $document) {
+
+    // only browsers that support these properties can render animations
+    if (!$sniffer.animations && !$sniffer.transitions) return noop;
+
+    var bodyNode = $document[0].body;
+    var rootNode = getDomNode($rootElement);
+
+    var rootBodyElement = jqLite(
+      // this is to avoid using something that exists outside of the body
+      // we also special case the doc fragment case because our unit test code
+      // appends the $rootElement to the body after the app has been bootstrapped
+      isDocumentFragment(rootNode) || bodyNode.contains(rootNode) ? rootNode : bodyNode
+    );
+
+    var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
+
+    return function initDriverFn(animationDetails) {
+      return animationDetails.from && animationDetails.to
+          ? prepareFromToAnchorAnimation(animationDetails.from,
+                                         animationDetails.to,
+                                         animationDetails.classes,
+                                         animationDetails.anchors)
+          : prepareRegularAnimation(animationDetails);
+    };
+
+    function filterCssClasses(classes) {
+      //remove all the `ng-` stuff
+      return classes.replace(/\bng-\S+\b/g, '');
+    }
+
+    function getUniqueValues(a, b) {
+      if (isString(a)) a = a.split(' ');
+      if (isString(b)) b = b.split(' ');
+      return a.filter(function(val) {
+        return b.indexOf(val) === -1;
+      }).join(' ');
+    }
+
+    function prepareAnchoredAnimation(classes, outAnchor, inAnchor) {
+      var clone = jqLite(getDomNode(outAnchor).cloneNode(true));
+      var startingClasses = filterCssClasses(getClassVal(clone));
+
+      outAnchor.addClass(NG_ANIMATE_SHIM_CLASS_NAME);
+      inAnchor.addClass(NG_ANIMATE_SHIM_CLASS_NAME);
+
+      clone.addClass(NG_ANIMATE_ANCHOR_CLASS_NAME);
+
+      rootBodyElement.append(clone);
+
+      var animatorIn, animatorOut = prepareOutAnimation();
+
+      // the user may not end up using the `out` animation and
+      // only making use of the `in` animation or vice-versa.
+      // In either case we should allow this and not assume the
+      // animation is over unless both animations are not used.
+      if (!animatorOut) {
+        animatorIn = prepareInAnimation();
+        if (!animatorIn) {
+          return end();
+        }
+      }
+
+      var startingAnimator = animatorOut || animatorIn;
+
+      return {
+        start: function() {
+          var runner;
+
+          var currentAnimation = startingAnimator.start();
+          currentAnimation.done(function() {
+            currentAnimation = null;
+            if (!animatorIn) {
+              animatorIn = prepareInAnimation();
+              if (animatorIn) {
+                currentAnimation = animatorIn.start();
+                currentAnimation.done(function() {
+                  currentAnimation = null;
+                  end();
+                  runner.complete();
+                });
+                return currentAnimation;
+              }
+            }
+            // in the event that there is no `in` animation
+            end();
+            runner.complete();
+          });
+
+          runner = new $$AnimateRunner({
+            end: endFn,
+            cancel: endFn
+          });
+
+          return runner;
+
+          function endFn() {
+            if (currentAnimation) {
+              currentAnimation.end();
+            }
+          }
+        }
+      };
+
+      function calculateAnchorStyles(anchor) {
+        var styles = {};
+
+        var coords = getDomNode(anchor).getBoundingClientRect();
+
+        // we iterate directly since safari messes up and doesn't return
+        // all the keys for the coords object when iterated
+        forEach(['width','height','top','left'], function(key) {
+          var value = coords[key];
+          switch (key) {
+            case 'top':
+              value += bodyNode.scrollTop;
+              break;
+            case 'left':
+              value += bodyNode.scrollLeft;
+              break;
+          }
+          styles[key] = Math.floor(value) + 'px';
+        });
+        return styles;
+      }
+
+      function prepareOutAnimation() {
+        var animator = $animateCss(clone, {
+          addClass: NG_OUT_ANCHOR_CLASS_NAME,
+          delay: true,
+          from: calculateAnchorStyles(outAnchor)
+        });
+
+        // read the comment within `prepareRegularAnimation` to understand
+        // why this check is necessary
+        return animator.$$willAnimate ? animator : null;
+      }
+
+      function getClassVal(element) {
+        return element.attr('class') || '';
+      }
+
+      function prepareInAnimation() {
+        var endingClasses = filterCssClasses(getClassVal(inAnchor));
+        var toAdd = getUniqueValues(endingClasses, startingClasses);
+        var toRemove = getUniqueValues(startingClasses, endingClasses);
+
+        var animator = $animateCss(clone, {
+          to: calculateAnchorStyles(inAnchor),
+          addClass: NG_IN_ANCHOR_CLASS_NAME + ' ' + toAdd,
+          removeClass: NG_OUT_ANCHOR_CLASS_NAME + ' ' + toRemove,
+          delay: true
+        });
+
+        // read the comment within `prepareRegularAnimation` to understand
+        // why this check is necessary
+        return animator.$$willAnimate ? animator : null;
+      }
+
+      function end() {
+        clone.remove();
+        outAnchor.removeClass(NG_ANIMATE_SHIM_CLASS_NAME);
+        inAnchor.removeClass(NG_ANIMATE_SHIM_CLASS_NAME);
+      }
+    }
+
+    function prepareFromToAnchorAnimation(from, to, classes, anchors) {
+      var fromAnimation = prepareRegularAnimation(from, noop);
+      var toAnimation = prepareRegularAnimation(to, noop);
+
+      var anchorAnimations = [];
+      forEach(anchors, function(anchor) {
+        var outElement = anchor['out'];
+        var inElement = anchor['in'];
+        var animator = prepareAnchoredAnimation(classes, outElement, inElement);
+        if (animator) {
+          anchorAnimations.push(animator);
+        }
+      });
+
+      // no point in doing anything when there are no elements to animate
+      if (!fromAnimation && !toAnimation && anchorAnimations.length === 0) return;
+
+      return {
+        start: function() {
+          var animationRunners = [];
+
+          if (fromAnimation) {
+            animationRunners.push(fromAnimation.start());
+          }
+
+          if (toAnimation) {
+            animationRunners.push(toAnimation.start());
+          }
+
+          forEach(anchorAnimations, function(animation) {
+            animationRunners.push(animation.start());
+          });
+
+          var runner = new $$AnimateRunner({
+            end: endFn,
+            cancel: endFn // CSS-driven animations cannot be cancelled, only ended
+          });
+
+          $$AnimateRunner.all(animationRunners, function(status) {
+            runner.complete(status);
+          });
+
+          return runner;
+
+          function endFn() {
+            forEach(animationRunners, function(runner) {
+              runner.end();
+            });
+          }
+        }
+      };
+    }
+
+    function prepareRegularAnimation(animationDetails) {
+      var element = animationDetails.element;
+      var options = animationDetails.options || {};
+
+      if (animationDetails.structural) {
+        options.event = animationDetails.event;
+        options.structural = true;
+        options.applyClassesEarly = true;
+
+        // we special case the leave animation since we want to ensure that
+        // the element is removed as soon as the animation is over. Otherwise
+        // a flicker might appear or the element may not be removed at all
+        if (animationDetails.event === 'leave') {
+          options.onDone = options.domOperation;
+        }
+      }
+
+      // We assign the preparationClasses as the actual animation event since
+      // the internals of $animateCss will just suffix the event token values
+      // with `-active` to trigger the animation.
+      if (options.preparationClasses) {
+        options.event = concatWithSpace(options.event, options.preparationClasses);
+      }
+
+      var animator = $animateCss(element, options);
+
+      // the driver lookup code inside of $$animation attempts to spawn a
+      // driver one by one until a driver returns a.$$willAnimate animator object.
+      // $animateCss will always return an object, however, it will pass in
+      // a flag as a hint as to whether an animation was detected or not
+      return animator.$$willAnimate ? animator : null;
+    }
+  }];
+}];
+
+// TODO(matsko): use caching here to speed things up for detection
+// TODO(matsko): add documentation
+//  by the time...
+
+var $$AnimateJsProvider = ['$animateProvider', function($animateProvider) {
+  this.$get = ['$injector', '$$AnimateRunner', '$$jqLite',
+       function($injector,   $$AnimateRunner,   $$jqLite) {
+
+    var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
+         // $animateJs(element, 'enter');
+    return function(element, event, classes, options) {
+      var animationClosed = false;
+
+      // the `classes` argument is optional and if it is not used
+      // then the classes will be resolved from the element's className
+      // property as well as options.addClass/options.removeClass.
+      if (arguments.length === 3 && isObject(classes)) {
+        options = classes;
+        classes = null;
+      }
+
+      options = prepareAnimationOptions(options);
+      if (!classes) {
+        classes = element.attr('class') || '';
+        if (options.addClass) {
+          classes += ' ' + options.addClass;
+        }
+        if (options.removeClass) {
+          classes += ' ' + options.removeClass;
+        }
+      }
+
+      var classesToAdd = options.addClass;
+      var classesToRemove = options.removeClass;
+
+      // the lookupAnimations function returns a series of animation objects that are
+      // matched up with one or more of the CSS classes. These animation objects are
+      // defined via the module.animation factory function. If nothing is detected then
+      // we don't return anything which then makes $animation query the next driver.
+      var animations = lookupAnimations(classes);
+      var before, after;
+      if (animations.length) {
+        var afterFn, beforeFn;
+        if (event == 'leave') {
+          beforeFn = 'leave';
+          afterFn = 'afterLeave'; // TODO(matsko): get rid of this
+        } else {
+          beforeFn = 'before' + event.charAt(0).toUpperCase() + event.substr(1);
+          afterFn = event;
+        }
+
+        if (event !== 'enter' && event !== 'move') {
+          before = packageAnimations(element, event, options, animations, beforeFn);
+        }
+        after  = packageAnimations(element, event, options, animations, afterFn);
+      }
+
+      // no matching animations
+      if (!before && !after) return;
+
+      function applyOptions() {
+        options.domOperation();
+        applyAnimationClasses(element, options);
+      }
+
+      function close() {
+        animationClosed = true;
+        applyOptions();
+        applyAnimationStyles(element, options);
+      }
+
+      var runner;
+
+      return {
+        $$willAnimate: true,
+        end: function() {
+          if (runner) {
+            runner.end();
+          } else {
+            close();
+            runner = new $$AnimateRunner();
+            runner.complete(true);
+          }
+          return runner;
+        },
+        start: function() {
+          if (runner) {
+            return runner;
+          }
+
+          runner = new $$AnimateRunner();
+          var closeActiveAnimations;
+          var chain = [];
+
+          if (before) {
+            chain.push(function(fn) {
+              closeActiveAnimations = before(fn);
+            });
+          }
+
+          if (chain.length) {
+            chain.push(function(fn) {
+              applyOptions();
+              fn(true);
+            });
+          } else {
+            applyOptions();
+          }
+
+          if (after) {
+            chain.push(function(fn) {
+              closeActiveAnimations = after(fn);
+            });
+          }
+
+          runner.setHost({
+            end: function() {
+              endAnimations();
+            },
+            cancel: function() {
+              endAnimations(true);
+            }
+          });
+
+          $$AnimateRunner.chain(chain, onComplete);
+          return runner;
+
+          function onComplete(success) {
+            close(success);
+            runner.complete(success);
+          }
+
+          function endAnimations(cancelled) {
+            if (!animationClosed) {
+              (closeActiveAnimations || noop)(cancelled);
+              onComplete(cancelled);
+            }
+          }
+        }
+      };
+
+      function executeAnimationFn(fn, element, event, options, onDone) {
+        var args;
+        switch (event) {
+          case 'animate':
+            args = [element, options.from, options.to, onDone];
+            break;
+
+          case 'setClass':
+            args = [element, classesToAdd, classesToRemove, onDone];
+            break;
+
+          case 'addClass':
+            args = [element, classesToAdd, onDone];
+            break;
+
+          case 'removeClass':
+            args = [element, classesToRemove, onDone];
+            break;
+
+          default:
+            args = [element, onDone];
+            break;
+        }
+
+        args.push(options);
+
+        var value = fn.apply(fn, args);
+        if (value) {
+          if (isFunction(value.start)) {
+            value = value.start();
+          }
+
+          if (value instanceof $$AnimateRunner) {
+            value.done(onDone);
+          } else if (isFunction(value)) {
+            // optional onEnd / onCancel callback
+            return value;
+          }
+        }
+
+        return noop;
+      }
+
+      function groupEventedAnimations(element, event, options, animations, fnName) {
+        var operations = [];
+        forEach(animations, function(ani) {
+          var animation = ani[fnName];
+          if (!animation) return;
+
+          // note that all of these animations will run in parallel
+          operations.push(function() {
+            var runner;
+            var endProgressCb;
+
+            var resolved = false;
+            var onAnimationComplete = function(rejected) {
+              if (!resolved) {
+                resolved = true;
+                (endProgressCb || noop)(rejected);
+                runner.complete(!rejected);
+              }
+            };
+
+            runner = new $$AnimateRunner({
+              end: function() {
+                onAnimationComplete();
+              },
+              cancel: function() {
+                onAnimationComplete(true);
+              }
+            });
+
+            endProgressCb = executeAnimationFn(animation, element, event, options, function(result) {
+              var cancelled = result === false;
+              onAnimationComplete(cancelled);
+            });
+
+            return runner;
+          });
+        });
+
+        return operations;
+      }
+
+      function packageAnimations(element, event, options, animations, fnName) {
+        var operations = groupEventedAnimations(element, event, options, animations, fnName);
+        if (operations.length === 0) {
+          var a,b;
+          if (fnName === 'beforeSetClass') {
+            a = groupEventedAnimations(element, 'removeClass', options, animations, 'beforeRemoveClass');
+            b = groupEventedAnimations(element, 'addClass', options, animations, 'beforeAddClass');
+          } else if (fnName === 'setClass') {
+            a = groupEventedAnimations(element, 'removeClass', options, animations, 'removeClass');
+            b = groupEventedAnimations(element, 'addClass', options, animations, 'addClass');
+          }
+
+          if (a) {
+            operations = operations.concat(a);
+          }
+          if (b) {
+            operations = operations.concat(b);
+          }
+        }
+
+        if (operations.length === 0) return;
+
+        // TODO(matsko): add documentation
+        return function startAnimation(callback) {
+          var runners = [];
+          if (operations.length) {
+            forEach(operations, function(animateFn) {
+              runners.push(animateFn());
+            });
+          }
+
+          runners.length ? $$AnimateRunner.all(runners, callback) : callback();
+
+          return function endFn(reject) {
+            forEach(runners, function(runner) {
+              reject ? runner.cancel() : runner.end();
+            });
+          };
+        };
+      }
+    };
+
+    function lookupAnimations(classes) {
+      classes = isArray(classes) ? classes : classes.split(' ');
+      var matches = [], flagMap = {};
+      for (var i=0; i < classes.length; i++) {
+        var klass = classes[i],
+            animationFactory = $animateProvider.$$registeredAnimations[klass];
+        if (animationFactory && !flagMap[klass]) {
+          matches.push($injector.get(animationFactory));
+          flagMap[klass] = true;
+        }
+      }
+      return matches;
+    }
+  }];
+}];
+
+var $$AnimateJsDriverProvider = ['$$animationProvider', function($$animationProvider) {
+  $$animationProvider.drivers.push('$$animateJsDriver');
+  this.$get = ['$$animateJs', '$$AnimateRunner', function($$animateJs, $$AnimateRunner) {
+    return function initDriverFn(animationDetails) {
+      if (animationDetails.from && animationDetails.to) {
+        var fromAnimation = prepareAnimation(animationDetails.from);
+        var toAnimation = prepareAnimation(animationDetails.to);
+        if (!fromAnimation && !toAnimation) return;
+
+        return {
+          start: function() {
+            var animationRunners = [];
+
+            if (fromAnimation) {
+              animationRunners.push(fromAnimation.start());
+            }
+
+            if (toAnimation) {
+              animationRunners.push(toAnimation.start());
+            }
+
+            $$AnimateRunner.all(animationRunners, done);
+
+            var runner = new $$AnimateRunner({
+              end: endFnFactory(),
+              cancel: endFnFactory()
+            });
+
+            return runner;
+
+            function endFnFactory() {
+              return function() {
+                forEach(animationRunners, function(runner) {
+                  // at this point we cannot cancel animations for groups just yet. 1.5+
+                  runner.end();
+                });
+              };
+            }
+
+            function done(status) {
+              runner.complete(status);
+            }
+          }
+        };
+      } else {
+        return prepareAnimation(animationDetails);
+      }
+    };
+
+    function prepareAnimation(animationDetails) {
+      // TODO(matsko): make sure to check for grouped animations and delegate down to normal animations
+      var element = animationDetails.element;
+      var event = animationDetails.event;
+      var options = animationDetails.options;
+      var classes = animationDetails.classes;
+      return $$animateJs(element, event, classes, options);
+    }
+  }];
+}];
+
+var NG_ANIMATE_ATTR_NAME = 'data-ng-animate';
+var NG_ANIMATE_PIN_DATA = '$ngAnimatePin';
+var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
+  var PRE_DIGEST_STATE = 1;
+  var RUNNING_STATE = 2;
+  var ONE_SPACE = ' ';
+
+  var rules = this.rules = {
+    skip: [],
+    cancel: [],
+    join: []
+  };
+
+  function makeTruthyCssClassMap(classString) {
+    if (!classString) {
+      return null;
+    }
+
+    var keys = classString.split(ONE_SPACE);
+    var map = Object.create(null);
+
+    forEach(keys, function(key) {
+      map[key] = true;
+    });
+    return map;
+  }
+
+  function hasMatchingClasses(newClassString, currentClassString) {
+    if (newClassString && currentClassString) {
+      var currentClassMap = makeTruthyCssClassMap(currentClassString);
+      return newClassString.split(ONE_SPACE).some(function(className) {
+        return currentClassMap[className];
+      });
+    }
+  }
+
+  function isAllowed(ruleType, element, currentAnimation, previousAnimation) {
+    return rules[ruleType].some(function(fn) {
+      return fn(element, currentAnimation, previousAnimation);
+    });
+  }
+
+  function hasAnimationClasses(animation, and) {
+    var a = (animation.addClass || '').length > 0;
+    var b = (animation.removeClass || '').length > 0;
+    return and ? a && b : a || b;
+  }
+
+  rules.join.push(function(element, newAnimation, currentAnimation) {
+    // if the new animation is class-based then we can just tack that on
+    return !newAnimation.structural && hasAnimationClasses(newAnimation);
+  });
+
+  rules.skip.push(function(element, newAnimation, currentAnimation) {
+    // there is no need to animate anything if no classes are being added and
+    // there is no structural animation that will be triggered
+    return !newAnimation.structural && !hasAnimationClasses(newAnimation);
+  });
+
+  rules.skip.push(function(element, newAnimation, currentAnimation) {
+    // why should we trigger a new structural animation if the element will
+    // be removed from the DOM anyway?
+    return currentAnimation.event == 'leave' && newAnimation.structural;
+  });
+
+  rules.skip.push(function(element, newAnimation, currentAnimation) {
+    // if there is an ongoing current animation then don't even bother running the class-based animation
+    return currentAnimation.structural && currentAnimation.state === RUNNING_STATE && !newAnimation.structural;
+  });
+
+  rules.cancel.push(function(element, newAnimation, currentAnimation) {
+    // there can never be two structural animations running at the same time
+    return currentAnimation.structural && newAnimation.structural;
+  });
+
+  rules.cancel.push(function(element, newAnimation, currentAnimation) {
+    // if the previous animation is already running, but the new animation will
+    // be triggered, but the new animation is structural
+    return currentAnimation.state === RUNNING_STATE && newAnimation.structural;
+  });
+
+  rules.cancel.push(function(element, newAnimation, currentAnimation) {
+    // cancel the animation if classes added / removed in both animation cancel each other out,
+    // but only if the current animation isn't structural
+
+    if (currentAnimation.structural) return false;
+
+    var nA = newAnimation.addClass;
+    var nR = newAnimation.removeClass;
+    var cA = currentAnimation.addClass;
+    var cR = currentAnimation.removeClass;
+
+    // early detection to save the global CPU shortage :)
+    if ((isUndefined(nA) && isUndefined(nR)) || (isUndefined(cA) && isUndefined(cR))) {
+      return false;
+    }
+
+    return hasMatchingClasses(nA, cR) || hasMatchingClasses(nR, cA);
+  });
+
+  this.$get = ['$$rAF', '$rootScope', '$rootElement', '$document', '$$HashMap',
+               '$$animation', '$$AnimateRunner', '$templateRequest', '$$jqLite', '$$forceReflow',
+       function($$rAF,   $rootScope,   $rootElement,   $document,   $$HashMap,
+                $$animation,   $$AnimateRunner,   $templateRequest,   $$jqLite,   $$forceReflow) {
+
+    var activeAnimationsLookup = new $$HashMap();
+    var disabledElementsLookup = new $$HashMap();
+    var animationsEnabled = null;
+
+    function postDigestTaskFactory() {
+      var postDigestCalled = false;
+      return function(fn) {
+        // we only issue a call to postDigest before
+        // it has first passed. This prevents any callbacks
+        // from not firing once the animation has completed
+        // since it will be out of the digest cycle.
+        if (postDigestCalled) {
+          fn();
+        } else {
+          $rootScope.$$postDigest(function() {
+            postDigestCalled = true;
+            fn();
+          });
+        }
+      };
+    }
+
+    // Wait until all directive and route-related templates are downloaded and
+    // compiled. The $templateRequest.totalPendingRequests variable keeps track of
+    // all of the remote templates being currently downloaded. If there are no
+    // templates currently downloading then the watcher will still fire anyway.
+    var deregisterWatch = $rootScope.$watch(
+      function() { return $templateRequest.totalPendingRequests === 0; },
+      function(isEmpty) {
+        if (!isEmpty) return;
+        deregisterWatch();
+
+        // Now that all templates have been downloaded, $animate will wait until
+        // the post digest queue is empty before enabling animations. By having two
+        // calls to $postDigest calls we can ensure that the flag is enabled at the
+        // very end of the post digest queue. Since all of the animations in $animate
+        // use $postDigest, it's important that the code below executes at the end.
+        // This basically means that the page is fully downloaded and compiled before
+        // any animations are triggered.
+        $rootScope.$$postDigest(function() {
+          $rootScope.$$postDigest(function() {
+            // we check for null directly in the event that the application already called
+            // .enabled() with whatever arguments that it provided it with
+            if (animationsEnabled === null) {
+              animationsEnabled = true;
+            }
+          });
+        });
+      }
+    );
+
+    var callbackRegistry = {};
+
+    // remember that the classNameFilter is set during the provider/config
+    // stage therefore we can optimize here and setup a helper function
+    var classNameFilter = $animateProvider.classNameFilter();
+    var isAnimatableClassName = !classNameFilter
+              ? function() { return true; }
+              : function(className) {
+                return classNameFilter.test(className);
+              };
+
+    var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
+
+    function normalizeAnimationDetails(element, animation) {
+      return mergeAnimationDetails(element, animation, {});
+    }
+
+    // IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259.
+    var contains = Node.prototype.contains || function(arg) {
+      // jshint bitwise: false
+      return this === arg || !!(this.compareDocumentPosition(arg) & 16);
+      // jshint bitwise: true
+    };
+
+    function findCallbacks(parent, element, event) {
+      var targetNode = getDomNode(element);
+      var targetParentNode = getDomNode(parent);
+
+      var matches = [];
+      var entries = callbackRegistry[event];
+      if (entries) {
+        forEach(entries, function(entry) {
+          if (contains.call(entry.node, targetNode)) {
+            matches.push(entry.callback);
+          } else if (event === 'leave' && contains.call(entry.node, targetParentNode)) {
+            matches.push(entry.callback);
+          }
+        });
+      }
+
+      return matches;
+    }
+
+    var $animate = {
+      on: function(event, container, callback) {
+        var node = extractElementNode(container);
+        callbackRegistry[event] = callbackRegistry[event] || [];
+        callbackRegistry[event].push({
+          node: node,
+          callback: callback
+        });
+
+        // Remove the callback when the element is removed from the DOM
+        jqLite(container).on('$destroy', function() {
+          $animate.off(event, container, callback);
+        });
+      },
+
+      off: function(event, container, callback) {
+        var entries = callbackRegistry[event];
+        if (!entries) return;
+
+        callbackRegistry[event] = arguments.length === 1
+            ? null
+            : filterFromRegistry(entries, container, callback);
+
+        function filterFromRegistry(list, matchContainer, matchCallback) {
+          var containerNode = extractElementNode(matchContainer);
+          return list.filter(function(entry) {
+            var isMatch = entry.node === containerNode &&
+                            (!matchCallback || entry.callback === matchCallback);
+            return !isMatch;
+          });
+        }
+      },
+
+      pin: function(element, parentElement) {
+        assertArg(isElement(element), 'element', 'not an element');
+        assertArg(isElement(parentElement), 'parentElement', 'not an element');
+        element.data(NG_ANIMATE_PIN_DATA, parentElement);
+      },
+
+      push: function(element, event, options, domOperation) {
+        options = options || {};
+        options.domOperation = domOperation;
+        return queueAnimation(element, event, options);
+      },
+
+      // this method has four signatures:
+      //  () - global getter
+      //  (bool) - global setter
+      //  (element) - element getter
+      //  (element, bool) - element setter<F37>
+      enabled: function(element, bool) {
+        var argCount = arguments.length;
+
+        if (argCount === 0) {
+          // () - Global getter
+          bool = !!animationsEnabled;
+        } else {
+          var hasElement = isElement(element);
+
+          if (!hasElement) {
+            // (bool) - Global setter
+            bool = animationsEnabled = !!element;
+          } else {
+            var node = getDomNode(element);
+            var recordExists = disabledElementsLookup.get(node);
+
+            if (argCount === 1) {
+              // (element) - Element getter
+              bool = !recordExists;
+            } else {
+              // (element, bool) - Element setter
+              disabledElementsLookup.put(node, !bool);
+            }
+          }
+        }
+
+        return bool;
+      }
+    };
+
+    return $animate;
+
+    function queueAnimation(element, event, initialOptions) {
+      // we always make a copy of the options since
+      // there should never be any side effects on
+      // the input data when running `$animateCss`.
+      var options = copy(initialOptions);
+
+      var node, parent;
+      element = stripCommentsFromElement(element);
+      if (element) {
+        node = getDomNode(element);
+        parent = element.parent();
+      }
+
+      options = prepareAnimationOptions(options);
+
+      // we create a fake runner with a working promise.
+      // These methods will become available after the digest has passed
+      var runner = new $$AnimateRunner();
+
+      // this is used to trigger callbacks in postDigest mode
+      var runInNextPostDigestOrNow = postDigestTaskFactory();
+
+      if (isArray(options.addClass)) {
+        options.addClass = options.addClass.join(' ');
+      }
+
+      if (options.addClass && !isString(options.addClass)) {
+        options.addClass = null;
+      }
+
+      if (isArray(options.removeClass)) {
+        options.removeClass = options.removeClass.join(' ');
+      }
+
+      if (options.removeClass && !isString(options.removeClass)) {
+        options.removeClass = null;
+      }
+
+      if (options.from && !isObject(options.from)) {
+        options.from = null;
+      }
+
+      if (options.to && !isObject(options.to)) {
+        options.to = null;
+      }
+
+      // there are situations where a directive issues an animation for
+      // a jqLite wrapper that contains only comment nodes... If this
+      // happens then there is no way we can perform an animation
+      if (!node) {
+        close();
+        return runner;
+      }
+
+      var className = [node.className, options.addClass, options.removeClass].join(' ');
+      if (!isAnimatableClassName(className)) {
+        close();
+        return runner;
+      }
+
+      var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0;
+
+      // this is a hard disable of all animations for the application or on
+      // the element itself, therefore  there is no need to continue further
+      // past this point if not enabled
+      // Animations are also disabled if the document is currently hidden (page is not visible
+      // to the user), because browsers slow down or do not flush calls to requestAnimationFrame
+      var skipAnimations = !animationsEnabled || $document[0].hidden || disabledElementsLookup.get(node);
+      var existingAnimation = (!skipAnimations && activeAnimationsLookup.get(node)) || {};
+      var hasExistingAnimation = !!existingAnimation.state;
+
+      // there is no point in traversing the same collection of parent ancestors if a followup
+      // animation will be run on the same element that already did all that checking work
+      if (!skipAnimations && (!hasExistingAnimation || existingAnimation.state != PRE_DIGEST_STATE)) {
+        skipAnimations = !areAnimationsAllowed(element, parent, event);
+      }
+
+      if (skipAnimations) {
+        close();
+        return runner;
+      }
+
+      if (isStructural) {
+        closeChildAnimations(element);
+      }
+
+      var newAnimation = {
+        structural: isStructural,
+        element: element,
+        event: event,
+        addClass: options.addClass,
+        removeClass: options.removeClass,
+        close: close,
+        options: options,
+        runner: runner
+      };
+
+      if (hasExistingAnimation) {
+        var skipAnimationFlag = isAllowed('skip', element, newAnimation, existingAnimation);
+        if (skipAnimationFlag) {
+          if (existingAnimation.state === RUNNING_STATE) {
+            close();
+            return runner;
+          } else {
+            mergeAnimationDetails(element, existingAnimation, newAnimation);
+            return existingAnimation.runner;
+          }
+        }
+        var cancelAnimationFlag = isAllowed('cancel', element, newAnimation, existingAnimation);
+        if (cancelAnimationFlag) {
+          if (existingAnimation.state === RUNNING_STATE) {
+            // this will end the animation right away and it is safe
+            // to do so since the animation is already running and the
+            // runner callback code will run in async
+            existingAnimation.runner.end();
+          } else if (existingAnimation.structural) {
+            // this means that the animation is queued into a digest, but
+            // hasn't started yet. Therefore it is safe to run the close
+            // method which will call the runner methods in async.
+            existingAnimation.close();
+          } else {
+            // this will merge the new animation options into existing animation options
+            mergeAnimationDetails(element, existingAnimation, newAnimation);
+
+            return existingAnimation.runner;
+          }
+        } else {
+          // a joined animation means that this animation will take over the existing one
+          // so an example would involve a leave animation taking over an enter. Then when
+          // the postDigest kicks in the enter will be ignored.
+          var joinAnimationFlag = isAllowed('join', element, newAnimation, existingAnimation);
+          if (joinAnimationFlag) {
+            if (existingAnimation.state === RUNNING_STATE) {
+              normalizeAnimationDetails(element, newAnimation);
+            } else {
+              applyGeneratedPreparationClasses(element, isStructural ? event : null, options);
+
+              event = newAnimation.event = existingAnimation.event;
+              options = mergeAnimationDetails(element, existingAnimation, newAnimation);
+
+              //we return the same runner since only the option values of this animation will
+              //be fed into the `existingAnimation`.
+              return existingAnimation.runner;
+            }
+          }
+        }
+      } else {
+        // normalization in this case means that it removes redundant CSS classes that
+        // already exist (addClass) or do not exist (removeClass) on the element
+        normalizeAnimationDetails(element, newAnimation);
+      }
+
+      // when the options are merged and cleaned up we may end up not having to do
+      // an animation at all, therefore we should check this before issuing a post
+      // digest callback. Structural animations will always run no matter what.
+      var isValidAnimation = newAnimation.structural;
+      if (!isValidAnimation) {
+        // animate (from/to) can be quickly checked first, otherwise we check if any classes are present
+        isValidAnimation = (newAnimation.event === 'animate' && Object.keys(newAnimation.options.to || {}).length > 0)
+                            || hasAnimationClasses(newAnimation);
+      }
+
+      if (!isValidAnimation) {
+        close();
+        clearElementAnimationState(element);
+        return runner;
+      }
+
+      // the counter keeps track of cancelled animations
+      var counter = (existingAnimation.counter || 0) + 1;
+      newAnimation.counter = counter;
+
+      markElementAnimationState(element, PRE_DIGEST_STATE, newAnimation);
+
+      $rootScope.$$postDigest(function() {
+        var animationDetails = activeAnimationsLookup.get(node);
+        var animationCancelled = !animationDetails;
+        animationDetails = animationDetails || {};
+
+        // if addClass/removeClass is called before something like enter then the
+        // registered parent element may not be present. The code below will ensure
+        // that a final value for parent element is obtained
+        var parentElement = element.parent() || [];
+
+        // animate/structural/class-based animations all have requirements. Otherwise there
+        // is no point in performing an animation. The parent node must also be set.
+        var isValidAnimation = parentElement.length > 0
+                                && (animationDetails.event === 'animate'
+                                    || animationDetails.structural
+                                    || hasAnimationClasses(animationDetails));
+
+        // this means that the previous animation was cancelled
+        // even if the follow-up animation is the same event
+        if (animationCancelled || animationDetails.counter !== counter || !isValidAnimation) {
+          // if another animation did not take over then we need
+          // to make sure that the domOperation and options are
+          // handled accordingly
+          if (animationCancelled) {
+            applyAnimationClasses(element, options);
+            applyAnimationStyles(element, options);
+          }
+
+          // if the event changed from something like enter to leave then we do
+          // it, otherwise if it's the same then the end result will be the same too
+          if (animationCancelled || (isStructural && animationDetails.event !== event)) {
+            options.domOperation();
+            runner.end();
+          }
+
+          // in the event that the element animation was not cancelled or a follow-up animation
+          // isn't allowed to animate from here then we need to clear the state of the element
+          // so that any future animations won't read the expired animation data.
+          if (!isValidAnimation) {
+            clearElementAnimationState(element);
+          }
+
+          return;
+        }
+
+        // this combined multiple class to addClass / removeClass into a setClass event
+        // so long as a structural event did not take over the animation
+        event = !animationDetails.structural && hasAnimationClasses(animationDetails, true)
+            ? 'setClass'
+            : animationDetails.event;
+
+        markElementAnimationState(element, RUNNING_STATE);
+        var realRunner = $$animation(element, event, animationDetails.options);
+
+        realRunner.done(function(status) {
+          close(!status);
+          var animationDetails = activeAnimationsLookup.get(node);
+          if (animationDetails && animationDetails.counter === counter) {
+            clearElementAnimationState(getDomNode(element));
+          }
+          notifyProgress(runner, event, 'close', {});
+        });
+
+        // this will update the runner's flow-control events based on
+        // the `realRunner` object.
+        runner.setHost(realRunner);
+        notifyProgress(runner, event, 'start', {});
+      });
+
+      return runner;
+
+      function notifyProgress(runner, event, phase, data) {
+        runInNextPostDigestOrNow(function() {
+          var callbacks = findCallbacks(parent, element, event);
+          if (callbacks.length) {
+            // do not optimize this call here to RAF because
+            // we don't know how heavy the callback code here will
+            // be and if this code is buffered then this can
+            // lead to a performance regression.
+            $$rAF(function() {
+              forEach(callbacks, function(callback) {
+                callback(element, phase, data);
+              });
+            });
+          }
+        });
+        runner.progress(event, phase, data);
+      }
+
+      function close(reject) { // jshint ignore:line
+        clearGeneratedClasses(element, options);
+        applyAnimationClasses(element, options);
+        applyAnimationStyles(element, options);
+        options.domOperation();
+        runner.complete(!reject);
+      }
+    }
+
+    function closeChildAnimations(element) {
+      var node = getDomNode(element);
+      var children = node.querySelectorAll('[' + NG_ANIMATE_ATTR_NAME + ']');
+      forEach(children, function(child) {
+        var state = parseInt(child.getAttribute(NG_ANIMATE_ATTR_NAME));
+        var animationDetails = activeAnimationsLookup.get(child);
+        if (animationDetails) {
+          switch (state) {
+            case RUNNING_STATE:
+              animationDetails.runner.end();
+              /* falls through */
+            case PRE_DIGEST_STATE:
+              activeAnimationsLookup.remove(child);
+              break;
+          }
+        }
+      });
+    }
+
+    function clearElementAnimationState(element) {
+      var node = getDomNode(element);
+      node.removeAttribute(NG_ANIMATE_ATTR_NAME);
+      activeAnimationsLookup.remove(node);
+    }
+
+    function isMatchingElement(nodeOrElmA, nodeOrElmB) {
+      return getDomNode(nodeOrElmA) === getDomNode(nodeOrElmB);
+    }
+
+    /**
+     * This fn returns false if any of the following is true:
+     * a) animations on any parent element are disabled, and animations on the element aren't explicitly allowed
+     * b) a parent element has an ongoing structural animation, and animateChildren is false
+     * c) the element is not a child of the body
+     * d) the element is not a child of the $rootElement
+     */
+    function areAnimationsAllowed(element, parentElement, event) {
+      var bodyElement = jqLite($document[0].body);
+      var bodyElementDetected = isMatchingElement(element, bodyElement) || element[0].nodeName === 'HTML';
+      var rootElementDetected = isMatchingElement(element, $rootElement);
+      var parentAnimationDetected = false;
+      var animateChildren;
+      var elementDisabled = disabledElementsLookup.get(getDomNode(element));
+
+      var parentHost = jqLite.data(element[0], NG_ANIMATE_PIN_DATA);
+      if (parentHost) {
+        parentElement = parentHost;
+      }
+
+      parentElement = getDomNode(parentElement);
+
+      while (parentElement) {
+        if (!rootElementDetected) {
+          // angular doesn't want to attempt to animate elements outside of the application
+          // therefore we need to ensure that the rootElement is an ancestor of the current element
+          rootElementDetected = isMatchingElement(parentElement, $rootElement);
+        }
+
+        if (parentElement.nodeType !== ELEMENT_NODE) {
+          // no point in inspecting the #document element
+          break;
+        }
+
+        var details = activeAnimationsLookup.get(parentElement) || {};
+        // either an enter, leave or move animation will commence
+        // therefore we can't allow any animations to take place
+        // but if a parent animation is class-based then that's ok
+        if (!parentAnimationDetected) {
+          var parentElementDisabled = disabledElementsLookup.get(parentElement);
+
+          if (parentElementDisabled === true && elementDisabled !== false) {
+            // disable animations if the user hasn't explicitly enabled animations on the
+            // current element
+            elementDisabled = true;
+            // element is disabled via parent element, no need to check anything else
+            break;
+          } else if (parentElementDisabled === false) {
+            elementDisabled = false;
+          }
+          parentAnimationDetected = details.structural;
+        }
+
+        if (isUndefined(animateChildren) || animateChildren === true) {
+          var value = jqLite.data(parentElement, NG_ANIMATE_CHILDREN_DATA);
+          if (isDefined(value)) {
+            animateChildren = value;
+          }
+        }
+
+        // there is no need to continue traversing at this point
+        if (parentAnimationDetected && animateChildren === false) break;
+
+        if (!bodyElementDetected) {
+          // we also need to ensure that the element is or will be a part of the body element
+          // otherwise it is pointless to even issue an animation to be rendered
+          bodyElementDetected = isMatchingElement(parentElement, bodyElement);
+        }
+
+        if (bodyElementDetected && rootElementDetected) {
+          // If both body and root have been found, any other checks are pointless,
+          // as no animation data should live outside the application
+          break;
+        }
+
+        if (!rootElementDetected) {
+          // If no rootElement is detected, check if the parentElement is pinned to another element
+          parentHost = jqLite.data(parentElement, NG_ANIMATE_PIN_DATA);
+          if (parentHost) {
+            // The pin target element becomes the next parent element
+            parentElement = getDomNode(parentHost);
+            continue;
+          }
+        }
+
+        parentElement = parentElement.parentNode;
+      }
+
+      var allowAnimation = (!parentAnimationDetected || animateChildren) && elementDisabled !== true;
+      return allowAnimation && rootElementDetected && bodyElementDetected;
+    }
+
+    function markElementAnimationState(element, state, details) {
+      details = details || {};
+      details.state = state;
+
+      var node = getDomNode(element);
+      node.setAttribute(NG_ANIMATE_ATTR_NAME, state);
+
+      var oldValue = activeAnimationsLookup.get(node);
+      var newValue = oldValue
+          ? extend(oldValue, details)
+          : details;
+      activeAnimationsLookup.put(node, newValue);
+    }
+  }];
+}];
+
+var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
+  var NG_ANIMATE_REF_ATTR = 'ng-animate-ref';
+
+  var drivers = this.drivers = [];
+
+  var RUNNER_STORAGE_KEY = '$$animationRunner';
+
+  function setRunner(element, runner) {
+    element.data(RUNNER_STORAGE_KEY, runner);
+  }
+
+  function removeRunner(element) {
+    element.removeData(RUNNER_STORAGE_KEY);
+  }
+
+  function getRunner(element) {
+    return element.data(RUNNER_STORAGE_KEY);
+  }
+
+  this.$get = ['$$jqLite', '$rootScope', '$injector', '$$AnimateRunner', '$$HashMap', '$$rAFScheduler',
+       function($$jqLite,   $rootScope,   $injector,   $$AnimateRunner,   $$HashMap,   $$rAFScheduler) {
+
+    var animationQueue = [];
+    var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
+
+    function sortAnimations(animations) {
+      var tree = { children: [] };
+      var i, lookup = new $$HashMap();
+
+      // this is done first beforehand so that the hashmap
+      // is filled with a list of the elements that will be animated
+      for (i = 0; i < animations.length; i++) {
+        var animation = animations[i];
+        lookup.put(animation.domNode, animations[i] = {
+          domNode: animation.domNode,
+          fn: animation.fn,
+          children: []
+        });
+      }
+
+      for (i = 0; i < animations.length; i++) {
+        processNode(animations[i]);
+      }
+
+      return flatten(tree);
+
+      function processNode(entry) {
+        if (entry.processed) return entry;
+        entry.processed = true;
+
+        var elementNode = entry.domNode;
+        var parentNode = elementNode.parentNode;
+        lookup.put(elementNode, entry);
+
+        var parentEntry;
+        while (parentNode) {
+          parentEntry = lookup.get(parentNode);
+          if (parentEntry) {
+            if (!parentEntry.processed) {
+              parentEntry = processNode(parentEntry);
+            }
+            break;
+          }
+          parentNode = parentNode.parentNode;
+        }
+
+        (parentEntry || tree).children.push(entry);
+        return entry;
+      }
+
+      function flatten(tree) {
+        var result = [];
+        var queue = [];
+        var i;
+
+        for (i = 0; i < tree.children.length; i++) {
+          queue.push(tree.children[i]);
+        }
+
+        var remainingLevelEntries = queue.length;
+        var nextLevelEntries = 0;
+        var row = [];
+
+        for (i = 0; i < queue.length; i++) {
+          var entry = queue[i];
+          if (remainingLevelEntries <= 0) {
+            remainingLevelEntries = nextLevelEntries;
+            nextLevelEntries = 0;
+            result.push(row);
+            row = [];
+          }
+          row.push(entry.fn);
+          entry.children.forEach(function(childEntry) {
+            nextLevelEntries++;
+            queue.push(childEntry);
+          });
+          remainingLevelEntries--;
+        }
+
+        if (row.length) {
+          result.push(row);
+        }
+
+        return result;
+      }
+    }
+
+    // TODO(matsko): document the signature in a better way
+    return function(element, event, options) {
+      options = prepareAnimationOptions(options);
+      var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0;
+
+      // there is no animation at the current moment, however
+      // these runner methods will get later updated with the
+      // methods leading into the driver's end/cancel methods
+      // for now they just stop the animation from starting
+      var runner = new $$AnimateRunner({
+        end: function() { close(); },
+        cancel: function() { close(true); }
+      });
+
+      if (!drivers.length) {
+        close();
+        return runner;
+      }
+
+      setRunner(element, runner);
+
+      var classes = mergeClasses(element.attr('class'), mergeClasses(options.addClass, options.removeClass));
+      var tempClasses = options.tempClasses;
+      if (tempClasses) {
+        classes += ' ' + tempClasses;
+        options.tempClasses = null;
+      }
+
+      var prepareClassName;
+      if (isStructural) {
+        prepareClassName = 'ng-' + event + PREPARE_CLASS_SUFFIX;
+        $$jqLite.addClass(element, prepareClassName);
+      }
+
+      animationQueue.push({
+        // this data is used by the postDigest code and passed into
+        // the driver step function
+        element: element,
+        classes: classes,
+        event: event,
+        structural: isStructural,
+        options: options,
+        beforeStart: beforeStart,
+        close: close
+      });
+
+      element.on('$destroy', handleDestroyedElement);
+
+      // we only want there to be one function called within the post digest
+      // block. This way we can group animations for all the animations that
+      // were apart of the same postDigest flush call.
+      if (animationQueue.length > 1) return runner;
+
+      $rootScope.$$postDigest(function() {
+        var animations = [];
+        forEach(animationQueue, function(entry) {
+          // the element was destroyed early on which removed the runner
+          // form its storage. This means we can't animate this element
+          // at all and it already has been closed due to destruction.
+          if (getRunner(entry.element)) {
+            animations.push(entry);
+          } else {
+            entry.close();
+          }
+        });
+
+        // now any future animations will be in another postDigest
+        animationQueue.length = 0;
+
+        var groupedAnimations = groupAnimations(animations);
+        var toBeSortedAnimations = [];
+
+        forEach(groupedAnimations, function(animationEntry) {
+          toBeSortedAnimations.push({
+            domNode: getDomNode(animationEntry.from ? animationEntry.from.element : animationEntry.element),
+            fn: function triggerAnimationStart() {
+              // it's important that we apply the `ng-animate` CSS class and the
+              // temporary classes before we do any driver invoking since these
+              // CSS classes may be required for proper CSS detection.
+              animationEntry.beforeStart();
+
+              var startAnimationFn, closeFn = animationEntry.close;
+
+              // in the event that the element was removed before the digest runs or
+              // during the RAF sequencing then we should not trigger the animation.
+              var targetElement = animationEntry.anchors
+                  ? (animationEntry.from.element || animationEntry.to.element)
+                  : animationEntry.element;
+
+              if (getRunner(targetElement)) {
+                var operation = invokeFirstDriver(animationEntry);
+                if (operation) {
+                  startAnimationFn = operation.start;
+                }
+              }
+
+              if (!startAnimationFn) {
+                closeFn();
+              } else {
+                var animationRunner = startAnimationFn();
+                animationRunner.done(function(status) {
+                  closeFn(!status);
+                });
+                updateAnimationRunners(animationEntry, animationRunner);
+              }
+            }
+          });
+        });
+
+        // we need to sort each of the animations in order of parent to child
+        // relationships. This ensures that the child classes are applied at the
+        // right time.
+        $$rAFScheduler(sortAnimations(toBeSortedAnimations));
+      });
+
+      return runner;
+
+      // TODO(matsko): change to reference nodes
+      function getAnchorNodes(node) {
+        var SELECTOR = '[' + NG_ANIMATE_REF_ATTR + ']';
+        var items = node.hasAttribute(NG_ANIMATE_REF_ATTR)
+              ? [node]
+              : node.querySelectorAll(SELECTOR);
+        var anchors = [];
+        forEach(items, function(node) {
+          var attr = node.getAttribute(NG_ANIMATE_REF_ATTR);
+          if (attr && attr.length) {
+            anchors.push(node);
+          }
+        });
+        return anchors;
+      }
+
+      function groupAnimations(animations) {
+        var preparedAnimations = [];
+        var refLookup = {};
+        forEach(animations, function(animation, index) {
+          var element = animation.element;
+          var node = getDomNode(element);
+          var event = animation.event;
+          var enterOrMove = ['enter', 'move'].indexOf(event) >= 0;
+          var anchorNodes = animation.structural ? getAnchorNodes(node) : [];
+
+          if (anchorNodes.length) {
+            var direction = enterOrMove ? 'to' : 'from';
+
+            forEach(anchorNodes, function(anchor) {
+              var key = anchor.getAttribute(NG_ANIMATE_REF_ATTR);
+              refLookup[key] = refLookup[key] || {};
+              refLookup[key][direction] = {
+                animationID: index,
+                element: jqLite(anchor)
+              };
+            });
+          } else {
+            preparedAnimations.push(animation);
+          }
+        });
+
+        var usedIndicesLookup = {};
+        var anchorGroups = {};
+        forEach(refLookup, function(operations, key) {
+          var from = operations.from;
+          var to = operations.to;
+
+          if (!from || !to) {
+            // only one of these is set therefore we can't have an
+            // anchor animation since all three pieces are required
+            var index = from ? from.animationID : to.animationID;
+            var indexKey = index.toString();
+            if (!usedIndicesLookup[indexKey]) {
+              usedIndicesLookup[indexKey] = true;
+              preparedAnimations.push(animations[index]);
+            }
+            return;
+          }
+
+          var fromAnimation = animations[from.animationID];
+          var toAnimation = animations[to.animationID];
+          var lookupKey = from.animationID.toString();
+          if (!anchorGroups[lookupKey]) {
+            var group = anchorGroups[lookupKey] = {
+              structural: true,
+              beforeStart: function() {
+                fromAnimation.beforeStart();
+                toAnimation.beforeStart();
+              },
+              close: function() {
+                fromAnimation.close();
+                toAnimation.close();
+              },
+              classes: cssClassesIntersection(fromAnimation.classes, toAnimation.classes),
+              from: fromAnimation,
+              to: toAnimation,
+              anchors: [] // TODO(matsko): change to reference nodes
+            };
+
+            // the anchor animations require that the from and to elements both have at least
+            // one shared CSS class which effectively marries the two elements together to use
+            // the same animation driver and to properly sequence the anchor animation.
+            if (group.classes.length) {
+              preparedAnimations.push(group);
+            } else {
+              preparedAnimations.push(fromAnimation);
+              preparedAnimations.push(toAnimation);
+            }
+          }
+
+          anchorGroups[lookupKey].anchors.push({
+            'out': from.element, 'in': to.element
+          });
+        });
+
+        return preparedAnimations;
+      }
+
+      function cssClassesIntersection(a,b) {
+        a = a.split(' ');
+        b = b.split(' ');
+        var matches = [];
+
+        for (var i = 0; i < a.length; i++) {
+          var aa = a[i];
+          if (aa.substring(0,3) === 'ng-') continue;
+
+          for (var j = 0; j < b.length; j++) {
+            if (aa === b[j]) {
+              matches.push(aa);
+              break;
+            }
+          }
+        }
+
+        return matches.join(' ');
+      }
+
+      function invokeFirstDriver(animationDetails) {
+        // we loop in reverse order since the more general drivers (like CSS and JS)
+        // may attempt more elements, but custom drivers are more particular
+        for (var i = drivers.length - 1; i >= 0; i--) {
+          var driverName = drivers[i];
+          if (!$injector.has(driverName)) continue; // TODO(matsko): remove this check
+
+          var factory = $injector.get(driverName);
+          var driver = factory(animationDetails);
+          if (driver) {
+            return driver;
+          }
+        }
+      }
+
+      function beforeStart() {
+        element.addClass(NG_ANIMATE_CLASSNAME);
+        if (tempClasses) {
+          $$jqLite.addClass(element, tempClasses);
+        }
+        if (prepareClassName) {
+          $$jqLite.removeClass(element, prepareClassName);
+          prepareClassName = null;
+        }
+      }
+
+      function updateAnimationRunners(animation, newRunner) {
+        if (animation.from && animation.to) {
+          update(animation.from.element);
+          update(animation.to.element);
+        } else {
+          update(animation.element);
+        }
+
+        function update(element) {
+          getRunner(element).setHost(newRunner);
+        }
+      }
+
+      function handleDestroyedElement() {
+        var runner = getRunner(element);
+        if (runner && (event !== 'leave' || !options.$$domOperationFired)) {
+          runner.end();
+        }
+      }
+
+      function close(rejected) { // jshint ignore:line
+        element.off('$destroy', handleDestroyedElement);
+        removeRunner(element);
+
+        applyAnimationClasses(element, options);
+        applyAnimationStyles(element, options);
+        options.domOperation();
+
+        if (tempClasses) {
+          $$jqLite.removeClass(element, tempClasses);
+        }
+
+        element.removeClass(NG_ANIMATE_CLASSNAME);
+        runner.complete(!rejected);
+      }
+    };
+  }];
+}];
+
+/**
+ * @ngdoc directive
+ * @name ngAnimateSwap
+ * @restrict A
+ * @scope
+ *
+ * @description
+ *
+ * ngAnimateSwap is a animation-oriented directive that allows for the container to
+ * be removed and entered in whenever the associated expression changes. A
+ * common usecase for this directive is a rotating banner or slider component which
+ * contains one image being present at a time. When the active image changes
+ * then the old image will perform a `leave` animation and the new element
+ * will be inserted via an `enter` animation.
+ *
+ * @animations
+ * | Animation                        | Occurs                               |
+ * |----------------------------------|--------------------------------------|
+ * | {@link ng.$animate#enter enter}  | when the new element is inserted to the DOM  |
+ * | {@link ng.$animate#leave leave}  | when the old element is removed from the DOM |
+ *
+ * @example
+ * <example name="ngAnimateSwap-directive" module="ngAnimateSwapExample"
+ *          deps="angular-animate.js"
+ *          animations="true" fixBase="true">
+ *   <file name="index.html">
+ *     <div class="container" ng-controller="AppCtrl">
+ *       <div ng-animate-swap="number" class="cell swap-animation" ng-class="colorClass(number)">
+ *         {{ number }}
+ *       </div>
+ *     </div>
+ *   </file>
+ *   <file name="script.js">
+ *     angular.module('ngAnimateSwapExample', ['ngAnimate'])
+ *       .controller('AppCtrl', ['$scope', '$interval', function($scope, $interval) {
+ *         $scope.number = 0;
+ *         $interval(function() {
+ *           $scope.number++;
+ *         }, 1000);
+ *
+ *         var colors = ['red','blue','green','yellow','orange'];
+ *         $scope.colorClass = function(number) {
+ *           return colors[number % colors.length];
+ *         };
+ *       }]);
+ *   </file>
+ *  <file name="animations.css">
+ *  .container {
+ *    height:250px;
+ *    width:250px;
+ *    position:relative;
+ *    overflow:hidden;
+ *    border:2px solid black;
+ *  }
+ *  .container .cell {
+ *    font-size:150px;
+ *    text-align:center;
+ *    line-height:250px;
+ *    position:absolute;
+ *    top:0;
+ *    left:0;
+ *    right:0;
+ *    border-bottom:2px solid black;
+ *  }
+ *  .swap-animation.ng-enter, .swap-animation.ng-leave {
+ *    transition:0.5s linear all;
+ *  }
+ *  .swap-animation.ng-enter {
+ *    top:-250px;
+ *  }
+ *  .swap-animation.ng-enter-active {
+ *    top:0px;
+ *  }
+ *  .swap-animation.ng-leave {
+ *    top:0px;
+ *  }
+ *  .swap-animation.ng-leave-active {
+ *    top:250px;
+ *  }
+ *  .red { background:red; }
+ *  .green { background:green; }
+ *  .blue { background:blue; }
+ *  .yellow { background:yellow; }
+ *  .orange { background:orange; }
+ *  </file>
+ * </example>
+ */
+var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $rootScope) {
+  return {
+    restrict: 'A',
+    transclude: 'element',
+    terminal: true,
+    priority: 600, // we use 600 here to ensure that the directive is caught before others
+    link: function(scope, $element, attrs, ctrl, $transclude) {
+      var previousElement, previousScope;
+      scope.$watchCollection(attrs.ngAnimateSwap || attrs['for'], function(value) {
+        if (previousElement) {
+          $animate.leave(previousElement);
+        }
+        if (previousScope) {
+          previousScope.$destroy();
+          previousScope = null;
+        }
+        if (value || value === 0) {
+          previousScope = scope.$new();
+          $transclude(previousScope, function(element) {
+            previousElement = element;
+            $animate.enter(element, null, $element);
+          });
+        }
+      });
+    }
+  };
+}];
+
+/* global angularAnimateModule: true,
+
+   ngAnimateSwapDirective,
+   $$AnimateAsyncRunFactory,
+   $$rAFSchedulerFactory,
+   $$AnimateChildrenDirective,
+   $$AnimateQueueProvider,
+   $$AnimationProvider,
+   $AnimateCssProvider,
+   $$AnimateCssDriverProvider,
+   $$AnimateJsProvider,
+   $$AnimateJsDriverProvider,
+*/
+
+/**
+ * @ngdoc module
+ * @name ngAnimate
+ * @description
+ *
+ * The `ngAnimate` module provides support for CSS-based animations (keyframes and transitions) as well as JavaScript-based animations via
+ * callback hooks. Animations are not enabled by default, however, by including `ngAnimate` the animation hooks are enabled for an Angular app.
+ *
+ * <div doc-module-components="ngAnimate"></div>
+ *
+ * # Usage
+ * Simply put, there are two ways to make use of animations when ngAnimate is used: by using **CSS** and **JavaScript**. The former works purely based
+ * using CSS (by using matching CSS selectors/styles) and the latter triggers animations that are registered via `module.animation()`. For
+ * both CSS and JS animations the sole requirement is to have a matching `CSS class` that exists both in the registered animation and within
+ * the HTML element that the animation will be triggered on.
+ *
+ * ## Directive Support
+ * The following directives are "animation aware":
+ *
+ * | Directive                                                                                                | Supported Animations                                                     |
+ * |----------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------|
+ * | {@link ng.directive:ngRepeat#animations ngRepeat}                                                        | enter, leave and move                                                    |
+ * | {@link ngRoute.directive:ngView#animations ngView}                                                       | enter and leave                                                          |
+ * | {@link ng.directive:ngInclude#animations ngInclude}                                                      | enter and leave                                                          |
+ * | {@link ng.directive:ngSwitch#animations ngSwitch}                                                        | enter and leave                                                          |
+ * | {@link ng.directive:ngIf#animations ngIf}                                                                | enter and leave                                                          |
+ * | {@link ng.directive:ngClass#animations ngClass}                                                          | add and remove (the CSS class(es) present)                               |
+ * | {@link ng.directive:ngShow#animations ngShow} & {@link ng.directive:ngHide#animations ngHide}            | add and remove (the ng-hide class value)                                 |
+ * | {@link ng.directive:form#animation-hooks form} & {@link ng.directive:ngModel#animation-hooks ngModel}    | add and remove (dirty, pristine, valid, invalid & all other validations) |
+ * | {@link module:ngMessages#animations ngMessages}                                                          | add and remove (ng-active & ng-inactive)                                 |
+ * | {@link module:ngMessages#animations ngMessage}                                                           | enter and leave                                                          |
+ *
+ * (More information can be found by visiting each the documentation associated with each directive.)
+ *
+ * ## CSS-based Animations
+ *
+ * CSS-based animations with ngAnimate are unique since they require no JavaScript code at all. By using a CSS class that we reference between our HTML
+ * and CSS code we can create an animation that will be picked up by Angular when an the underlying directive performs an operation.
+ *
+ * The example below shows how an `enter` animation can be made possible on an element using `ng-if`:
+ *
+ * ```html
+ * <div ng-if="bool" class="fade">
+ *    Fade me in out
+ * </div>
+ * <button ng-click="bool=true">Fade In!</button>
+ * <button ng-click="bool=false">Fade Out!</button>
+ * ```
+ *
+ * Notice the CSS class **fade**? We can now create the CSS transition code that references this class:
+ *
+ * ```css
+ * /&#42; The starting CSS styles for the enter animation &#42;/
+ * .fade.ng-enter {
+ *   transition:0.5s linear all;
+ *   opacity:0;
+ * }
+ *
+ * /&#42; The finishing CSS styles for the enter animation &#42;/
+ * .fade.ng-enter.ng-enter-active {
+ *   opacity:1;
+ * }
+ * ```
+ *
+ * The key thing to remember here is that, depending on the animation event (which each of the directives above trigger depending on what's going on) two
+ * generated CSS classes will be applied to the element; in the example above we have `.ng-enter` and `.ng-enter-active`. For CSS transitions, the transition
+ * code **must** be defined within the starting CSS class (in this case `.ng-enter`). The destination class is what the transition will animate towards.
+ *
+ * If for example we wanted to create animations for `leave` and `move` (ngRepeat triggers move) then we can do so using the same CSS naming conventions:
+ *
+ * ```css
+ * /&#42; now the element will fade out before it is removed from the DOM &#42;/
+ * .fade.ng-leave {
+ *   transition:0.5s linear all;
+ *   opacity:1;
+ * }
+ * .fade.ng-leave.ng-leave-active {
+ *   opacity:0;
+ * }
+ * ```
+ *
+ * We can also make use of **CSS Keyframes** by referencing the keyframe animation within the starting CSS class:
+ *
+ * ```css
+ * /&#42; there is no need to define anything inside of the destination
+ * CSS class since the keyframe will take charge of the animation &#42;/
+ * .fade.ng-leave {
+ *   animation: my_fade_animation 0.5s linear;
+ *   -webkit-animation: my_fade_animation 0.5s linear;
+ * }
+ *
+ * @keyframes my_fade_animation {
+ *   from { opacity:1; }
+ *   to { opacity:0; }
+ * }
+ *
+ * @-webkit-keyframes my_fade_animation {
+ *   from { opacity:1; }
+ *   to { opacity:0; }
+ * }
+ * ```
+ *
+ * Feel free also mix transitions and keyframes together as well as any other CSS classes on the same element.
+ *
+ * ### CSS Class-based Animations
+ *
+ * Class-based animations (animations that are triggered via `ngClass`, `ngShow`, `ngHide` and some other directives) have a slightly different
+ * naming convention. Class-based animations are basic enough that a standard transition or keyframe can be referenced on the class being added
+ * and removed.
+ *
+ * For example if we wanted to do a CSS animation for `ngHide` then we place an animation on the `.ng-hide` CSS class:
+ *
+ * ```html
+ * <div ng-show="bool" class="fade">
+ *   Show and hide me
+ * </div>
+ * <button ng-click="bool=true">Toggle</button>
+ *
+ * <style>
+ * .fade.ng-hide {
+ *   transition:0.5s linear all;
+ *   opacity:0;
+ * }
+ * </style>
+ * ```
+ *
+ * All that is going on here with ngShow/ngHide behind the scenes is the `.ng-hide` class is added/removed (when the hidden state is valid). Since
+ * ngShow and ngHide are animation aware then we can match up a transition and ngAnimate handles the rest.
+ *
+ * In addition the addition and removal of the CSS class, ngAnimate also provides two helper methods that we can use to further decorate the animation
+ * with CSS styles.
+ *
+ * ```html
+ * <div ng-class="{on:onOff}" class="highlight">
+ *   Highlight this box
+ * </div>
+ * <button ng-click="onOff=!onOff">Toggle</button>
+ *
+ * <style>
+ * .highlight {
+ *   transition:0.5s linear all;
+ * }
+ * .highlight.on-add {
+ *   background:white;
+ * }
+ * .highlight.on {
+ *   background:yellow;
+ * }
+ * .highlight.on-remove {
+ *   background:black;
+ * }
+ * </style>
+ * ```
+ *
+ * We can also make use of CSS keyframes by placing them within the CSS classes.
+ *
+ *
+ * ### CSS Staggering Animations
+ * A Staggering animation is a collection of animations that are issued with a slight delay in between each successive operation resulting in a
+ * curtain-like effect. The ngAnimate module (versions >=1.2) supports staggering animations and the stagger effect can be
+ * performed by creating a **ng-EVENT-stagger** CSS class and attaching that class to the base CSS class used for
+ * the animation. The style property expected within the stagger class can either be a **transition-delay** or an
+ * **animation-delay** property (or both if your animation contains both transitions and keyframe animations).
+ *
+ * ```css
+ * .my-animation.ng-enter {
+ *   /&#42; standard transition code &#42;/
+ *   transition: 1s linear all;
+ *   opacity:0;
+ * }
+ * .my-animation.ng-enter-stagger {
+ *   /&#42; this will have a 100ms delay between each successive leave animation &#42;/
+ *   transition-delay: 0.1s;
+ *
+ *   /&#42; As of 1.4.4, this must always be set: it signals ngAnimate
+ *     to not accidentally inherit a delay property from another CSS class &#42;/
+ *   transition-duration: 0s;
+ * }
+ * .my-animation.ng-enter.ng-enter-active {
+ *   /&#42; standard transition styles &#42;/
+ *   opacity:1;
+ * }
+ * ```
+ *
+ * Staggering animations work by default in ngRepeat (so long as the CSS class is defined). Outside of ngRepeat, to use staggering animations
+ * on your own, they can be triggered by firing multiple calls to the same event on $animate. However, the restrictions surrounding this
+ * are that each of the elements must have the same CSS className value as well as the same parent element. A stagger operation
+ * will also be reset if one or more animation frames have passed since the multiple calls to `$animate` were fired.
+ *
+ * The following code will issue the **ng-leave-stagger** event on the element provided:
+ *
+ * ```js
+ * var kids = parent.children();
+ *
+ * $animate.leave(kids[0]); //stagger index=0
+ * $animate.leave(kids[1]); //stagger index=1
+ * $animate.leave(kids[2]); //stagger index=2
+ * $animate.leave(kids[3]); //stagger index=3
+ * $animate.leave(kids[4]); //stagger index=4
+ *
+ * window.requestAnimationFrame(function() {
+ *   //stagger has reset itself
+ *   $animate.leave(kids[5]); //stagger index=0
+ *   $animate.leave(kids[6]); //stagger index=1
+ *
+ *   $scope.$digest();
+ * });
+ * ```
+ *
+ * Stagger animations are currently only supported within CSS-defined animations.
+ *
+ * ### The `ng-animate` CSS class
+ *
+ * When ngAnimate is animating an element it will apply the `ng-animate` CSS class to the element for the duration of the animation.
+ * This is a temporary CSS class and it will be removed once the animation is over (for both JavaScript and CSS-based animations).
+ *
+ * Therefore, animations can be applied to an element using this temporary class directly via CSS.
+ *
+ * ```css
+ * .zipper.ng-animate {
+ *   transition:0.5s linear all;
+ * }
+ * .zipper.ng-enter {
+ *   opacity:0;
+ * }
+ * .zipper.ng-enter.ng-enter-active {
+ *   opacity:1;
+ * }
+ * .zipper.ng-leave {
+ *   opacity:1;
+ * }
+ * .zipper.ng-leave.ng-leave-active {
+ *   opacity:0;
+ * }
+ * ```
+ *
+ * (Note that the `ng-animate` CSS class is reserved and it cannot be applied on an element directly since ngAnimate will always remove
+ * the CSS class once an animation has completed.)
+ *
+ *
+ * ### The `ng-[event]-prepare` class
+ *
+ * This is a special class that can be used to prevent unwanted flickering / flash of content before
+ * the actual animation starts. The class is added as soon as an animation is initialized, but removed
+ * before the actual animation starts (after waiting for a $digest).
+ * It is also only added for *structural* animations (`enter`, `move`, and `leave`).
+ *
+ * In practice, flickering can appear when nesting elements with structural animations such as `ngIf`
+ * into elements that have class-based animations such as `ngClass`.
+ *
+ * ```html
+ * <div ng-class="{red: myProp}">
+ *   <div ng-class="{blue: myProp}">
+ *     <div class="message" ng-if="myProp"></div>
+ *   </div>
+ * </div>
+ * ```
+ *
+ * It is possible that during the `enter` animation, the `.message` div will be briefly visible before it starts animating.
+ * In that case, you can add styles to the CSS that make sure the element stays hidden before the animation starts:
+ *
+ * ```css
+ * .message.ng-enter-prepare {
+ *   opacity: 0;
+ * }
+ *
+ * ```
+ *
+ * ## JavaScript-based Animations
+ *
+ * ngAnimate also allows for animations to be consumed by JavaScript code. The approach is similar to CSS-based animations (where there is a shared
+ * CSS class that is referenced in our HTML code) but in addition we need to register the JavaScript animation on the module. By making use of the
+ * `module.animation()` module function we can register the animation.
+ *
+ * Let's see an example of a enter/leave animation using `ngRepeat`:
+ *
+ * ```html
+ * <div ng-repeat="item in items" class="slide">
+ *   {{ item }}
+ * </div>
+ * ```
+ *
+ * See the **slide** CSS class? Let's use that class to define an animation that we'll structure in our module code by using `module.animation`:
+ *
+ * ```js
+ * myModule.animation('.slide', [function() {
+ *   return {
+ *     // make note that other events (like addClass/removeClass)
+ *     // have different function input parameters
+ *     enter: function(element, doneFn) {
+ *       jQuery(element).fadeIn(1000, doneFn);
+ *
+ *       // remember to call doneFn so that angular
+ *       // knows that the animation has concluded
+ *     },
+ *
+ *     move: function(element, doneFn) {
+ *       jQuery(element).fadeIn(1000, doneFn);
+ *     },
+ *
+ *     leave: function(element, doneFn) {
+ *       jQuery(element).fadeOut(1000, doneFn);
+ *     }
+ *   }
+ * }]);
+ * ```
+ *
+ * The nice thing about JS-based animations is that we can inject other services and make use of advanced animation libraries such as
+ * greensock.js and velocity.js.
+ *
+ * If our animation code class-based (meaning that something like `ngClass`, `ngHide` and `ngShow` triggers it) then we can still define
+ * our animations inside of the same registered animation, however, the function input arguments are a bit different:
+ *
+ * ```html
+ * <div ng-class="color" class="colorful">
+ *   this box is moody
+ * </div>
+ * <button ng-click="color='red'">Change to red</button>
+ * <button ng-click="color='blue'">Change to blue</button>
+ * <button ng-click="color='green'">Change to green</button>
+ * ```
+ *
+ * ```js
+ * myModule.animation('.colorful', [function() {
+ *   return {
+ *     addClass: function(element, className, doneFn) {
+ *       // do some cool animation and call the doneFn
+ *     },
+ *     removeClass: function(element, className, doneFn) {
+ *       // do some cool animation and call the doneFn
+ *     },
+ *     setClass: function(element, addedClass, removedClass, doneFn) {
+ *       // do some cool animation and call the doneFn
+ *     }
+ *   }
+ * }]);
+ * ```
+ *
+ * ## CSS + JS Animations Together
+ *
+ * AngularJS 1.4 and higher has taken steps to make the amalgamation of CSS and JS animations more flexible. However, unlike earlier versions of Angular,
+ * defining CSS and JS animations to work off of the same CSS class will not work anymore. Therefore the example below will only result in **JS animations taking
+ * charge of the animation**:
+ *
+ * ```html
+ * <div ng-if="bool" class="slide">
+ *   Slide in and out
+ * </div>
+ * ```
+ *
+ * ```js
+ * myModule.animation('.slide', [function() {
+ *   return {
+ *     enter: function(element, doneFn) {
+ *       jQuery(element).slideIn(1000, doneFn);
+ *     }
+ *   }
+ * }]);
+ * ```
+ *
+ * ```css
+ * .slide.ng-enter {
+ *   transition:0.5s linear all;
+ *   transform:translateY(-100px);
+ * }
+ * .slide.ng-enter.ng-enter-active {
+ *   transform:translateY(0);
+ * }
+ * ```
+ *
+ * Does this mean that CSS and JS animations cannot be used together? Do JS-based animations always have higher priority? We can make up for the
+ * lack of CSS animations by using the `$animateCss` service to trigger our own tweaked-out, CSS-based animations directly from
+ * our own JS-based animation code:
+ *
+ * ```js
+ * myModule.animation('.slide', ['$animateCss', function($animateCss) {
+ *   return {
+ *     enter: function(element) {
+*        // this will trigger `.slide.ng-enter` and `.slide.ng-enter-active`.
+ *       return $animateCss(element, {
+ *         event: 'enter',
+ *         structural: true
+ *       });
+ *     }
+ *   }
+ * }]);
+ * ```
+ *
+ * The nice thing here is that we can save bandwidth by sticking to our CSS-based animation code and we don't need to rely on a 3rd-party animation framework.
+ *
+ * The `$animateCss` service is very powerful since we can feed in all kinds of extra properties that will be evaluated and fed into a CSS transition or
+ * keyframe animation. For example if we wanted to animate the height of an element while adding and removing classes then we can do so by providing that
+ * data into `$animateCss` directly:
+ *
+ * ```js
+ * myModule.animation('.slide', ['$animateCss', function($animateCss) {
+ *   return {
+ *     enter: function(element) {
+ *       return $animateCss(element, {
+ *         event: 'enter',
+ *         structural: true,
+ *         addClass: 'maroon-setting',
+ *         from: { height:0 },
+ *         to: { height: 200 }
+ *       });
+ *     }
+ *   }
+ * }]);
+ * ```
+ *
+ * Now we can fill in the rest via our transition CSS code:
+ *
+ * ```css
+ * /&#42; the transition tells ngAnimate to make the animation happen &#42;/
+ * .slide.ng-enter { transition:0.5s linear all; }
+ *
+ * /&#42; this extra CSS class will be absorbed into the transition
+ * since the $animateCss code is adding the class &#42;/
+ * .maroon-setting { background:red; }
+ * ```
+ *
+ * And `$animateCss` will figure out the rest. Just make sure to have the `done()` callback fire the `doneFn` function to signal when the animation is over.
+ *
+ * To learn more about what's possible be sure to visit the {@link ngAnimate.$animateCss $animateCss service}.
+ *
+ * ## Animation Anchoring (via `ng-animate-ref`)
+ *
+ * ngAnimate in AngularJS 1.4 comes packed with the ability to cross-animate elements between
+ * structural areas of an application (like views) by pairing up elements using an attribute
+ * called `ng-animate-ref`.
+ *
+ * Let's say for example we have two views that are managed by `ng-view` and we want to show
+ * that there is a relationship between two components situated in within these views. By using the
+ * `ng-animate-ref` attribute we can identify that the two components are paired together and we
+ * can then attach an animation, which is triggered when the view changes.
+ *
+ * Say for example we have the following template code:
+ *
+ * ```html
+ * <!-- index.html -->
+ * <div ng-view class="view-animation">
+ * </div>
+ *
+ * <!-- home.html -->
+ * <a href="#/banner-page">
+ *   <img src="./banner.jpg" class="banner" ng-animate-ref="banner">
+ * </a>
+ *
+ * <!-- banner-page.html -->
+ * <img src="./banner.jpg" class="banner" ng-animate-ref="banner">
+ * ```
+ *
+ * Now, when the view changes (once the link is clicked), ngAnimate will examine the
+ * HTML contents to see if there is a match reference between any components in the view
+ * that is leaving and the view that is entering. It will scan both the view which is being
+ * removed (leave) and inserted (enter) to see if there are any paired DOM elements that
+ * contain a matching ref value.
+ *
+ * The two images match since they share the same ref value. ngAnimate will now create a
+ * transport element (which is a clone of the first image element) and it will then attempt
+ * to animate to the position of the second image element in the next view. For the animation to
+ * work a special CSS class called `ng-anchor` will be added to the transported element.
+ *
+ * We can now attach a transition onto the `.banner.ng-anchor` CSS class and then
+ * ngAnimate will handle the entire transition for us as well as the addition and removal of
+ * any changes of CSS classes between the elements:
+ *
+ * ```css
+ * .banner.ng-anchor {
+ *   /&#42; this animation will last for 1 second since there are
+ *          two phases to the animation (an `in` and an `out` phase) &#42;/
+ *   transition:0.5s linear all;
+ * }
+ * ```
+ *
+ * We also **must** include animations for the views that are being entered and removed
+ * (otherwise anchoring wouldn't be possible since the new view would be inserted right away).
+ *
+ * ```css
+ * .view-animation.ng-enter, .view-animation.ng-leave {
+ *   transition:0.5s linear all;
+ *   position:fixed;
+ *   left:0;
+ *   top:0;
+ *   width:100%;
+ * }
+ * .view-animation.ng-enter {
+ *   transform:translateX(100%);
+ * }
+ * .view-animation.ng-leave,
+ * .view-animation.ng-enter.ng-enter-active {
+ *   transform:translateX(0%);
+ * }
+ * .view-animation.ng-leave.ng-leave-active {
+ *   transform:translateX(-100%);
+ * }
+ * ```
+ *
+ * Now we can jump back to the anchor animation. When the animation happens, there are two stages that occur:
+ * an `out` and an `in` stage. The `out` stage happens first and that is when the element is animated away
+ * from its origin. Once that animation is over then the `in` stage occurs which animates the
+ * element to its destination. The reason why there are two animations is to give enough time
+ * for the enter animation on the new element to be ready.
+ *
+ * The example above sets up a transition for both the in and out phases, but we can also target the out or
+ * in phases directly via `ng-anchor-out` and `ng-anchor-in`.
+ *
+ * ```css
+ * .banner.ng-anchor-out {
+ *   transition: 0.5s linear all;
+ *
+ *   /&#42; the scale will be applied during the out animation,
+ *          but will be animated away when the in animation runs &#42;/
+ *   transform: scale(1.2);
+ * }
+ *
+ * .banner.ng-anchor-in {
+ *   transition: 1s linear all;
+ * }
+ * ```
+ *
+ *
+ *
+ *
+ * ### Anchoring Demo
+ *
+  <example module="anchoringExample"
+           name="anchoringExample"
+           id="anchoringExample"
+           deps="angular-animate.js;angular-route.js"
+           animations="true">
+    <file name="index.html">
+      <a href="#/">Home</a>
+      <hr />
+      <div class="view-container">
+        <div ng-view class="view"></div>
+      </div>
+    </file>
+    <file name="script.js">
+      angular.module('anchoringExample', ['ngAnimate', 'ngRoute'])
+        .config(['$routeProvider', function($routeProvider) {
+          $routeProvider.when('/', {
+            templateUrl: 'home.html',
+            controller: 'HomeController as home'
+          });
+          $routeProvider.when('/profile/:id', {
+            templateUrl: 'profile.html',
+            controller: 'ProfileController as profile'
+          });
+        }])
+        .run(['$rootScope', function($rootScope) {
+          $rootScope.records = [
+            { id:1, title: "Miss Beulah Roob" },
+            { id:2, title: "Trent Morissette" },
+            { id:3, title: "Miss Ava Pouros" },
+            { id:4, title: "Rod Pouros" },
+            { id:5, title: "Abdul Rice" },
+            { id:6, title: "Laurie Rutherford Sr." },
+            { id:7, title: "Nakia McLaughlin" },
+            { id:8, title: "Jordon Blanda DVM" },
+            { id:9, title: "Rhoda Hand" },
+            { id:10, title: "Alexandrea Sauer" }
+          ];
+        }])
+        .controller('HomeController', [function() {
+          //empty
+        }])
+        .controller('ProfileController', ['$rootScope', '$routeParams', function($rootScope, $routeParams) {
+          var index = parseInt($routeParams.id, 10);
+          var record = $rootScope.records[index - 1];
+
+          this.title = record.title;
+          this.id = record.id;
+        }]);
+    </file>
+    <file name="home.html">
+      <h2>Welcome to the home page</h1>
+      <p>Please click on an element</p>
+      <a class="record"
+         ng-href="#/profile/{{ record.id }}"
+         ng-animate-ref="{{ record.id }}"
+         ng-repeat="record in records">
+        {{ record.title }}
+      </a>
+    </file>
+    <file name="profile.html">
+      <div class="profile record" ng-animate-ref="{{ profile.id }}">
+        {{ profile.title }}
+      </div>
+    </file>
+    <file name="animations.css">
+      .record {
+        display:block;
+        font-size:20px;
+      }
+      .profile {
+        background:black;
+        color:white;
+        font-size:100px;
+      }
+      .view-container {
+        position:relative;
+      }
+      .view-container > .view.ng-animate {
+        position:absolute;
+        top:0;
+        left:0;
+        width:100%;
+        min-height:500px;
+      }
+      .view.ng-enter, .view.ng-leave,
+      .record.ng-anchor {
+        transition:0.5s linear all;
+      }
+      .view.ng-enter {
+        transform:translateX(100%);
+      }
+      .view.ng-enter.ng-enter-active, .view.ng-leave {
+        transform:translateX(0%);
+      }
+      .view.ng-leave.ng-leave-active {
+        transform:translateX(-100%);
+      }
+      .record.ng-anchor-out {
+        background:red;
+      }
+    </file>
+  </example>
+ *
+ * ### How is the element transported?
+ *
+ * When an anchor animation occurs, ngAnimate will clone the starting element and position it exactly where the starting
+ * element is located on screen via absolute positioning. The cloned element will be placed inside of the root element
+ * of the application (where ng-app was defined) and all of the CSS classes of the starting element will be applied. The
+ * element will then animate into the `out` and `in` animations and will eventually reach the coordinates and match
+ * the dimensions of the destination element. During the entire animation a CSS class of `.ng-animate-shim` will be applied
+ * to both the starting and destination elements in order to hide them from being visible (the CSS styling for the class
+ * is: `visibility:hidden`). Once the anchor reaches its destination then it will be removed and the destination element
+ * will become visible since the shim class will be removed.
+ *
+ * ### How is the morphing handled?
+ *
+ * CSS Anchoring relies on transitions and keyframes and the internal code is intelligent enough to figure out
+ * what CSS classes differ between the starting element and the destination element. These different CSS classes
+ * will be added/removed on the anchor element and a transition will be applied (the transition that is provided
+ * in the anchor class). Long story short, ngAnimate will figure out what classes to add and remove which will
+ * make the transition of the element as smooth and automatic as possible. Be sure to use simple CSS classes that
+ * do not rely on DOM nesting structure so that the anchor element appears the same as the starting element (since
+ * the cloned element is placed inside of root element which is likely close to the body element).
+ *
+ * Note that if the root element is on the `<html>` element then the cloned node will be placed inside of body.
+ *
+ *
+ * ## Using $animate in your directive code
+ *
+ * So far we've explored how to feed in animations into an Angular application, but how do we trigger animations within our own directives in our application?
+ * By injecting the `$animate` service into our directive code, we can trigger structural and class-based hooks which can then be consumed by animations. Let's
+ * imagine we have a greeting box that shows and hides itself when the data changes
+ *
+ * ```html
+ * <greeting-box active="onOrOff">Hi there</greeting-box>
+ * ```
+ *
+ * ```js
+ * ngModule.directive('greetingBox', ['$animate', function($animate) {
+ *   return function(scope, element, attrs) {
+ *     attrs.$observe('active', function(value) {
+ *       value ? $animate.addClass(element, 'on') : $animate.removeClass(element, 'on');
+ *     });
+ *   });
+ * }]);
+ * ```
+ *
+ * Now the `on` CSS class is added and removed on the greeting box component. Now if we add a CSS class on top of the greeting box element
+ * in our HTML code then we can trigger a CSS or JS animation to happen.
+ *
+ * ```css
+ * /&#42; normally we would create a CSS class to reference on the element &#42;/
+ * greeting-box.on { transition:0.5s linear all; background:green; color:white; }
+ * ```
+ *
+ * The `$animate` service contains a variety of other methods like `enter`, `leave`, `animate` and `setClass`. To learn more about what's
+ * possible be sure to visit the {@link ng.$animate $animate service API page}.
+ *
+ *
+ * ## Callbacks and Promises
+ *
+ * When `$animate` is called it returns a promise that can be used to capture when the animation has ended. Therefore if we were to trigger
+ * an animation (within our directive code) then we can continue performing directive and scope related activities after the animation has
+ * ended by chaining onto the returned promise that animation method returns.
+ *
+ * ```js
+ * // somewhere within the depths of the directive
+ * $animate.enter(element, parent).then(function() {
+ *   //the animation has completed
+ * });
+ * ```
+ *
+ * (Note that earlier versions of Angular prior to v1.4 required the promise code to be wrapped using `$scope.$apply(...)`. This is not the case
+ * anymore.)
+ *
+ * In addition to the animation promise, we can also make use of animation-related callbacks within our directives and controller code by registering
+ * an event listener using the `$animate` service. Let's say for example that an animation was triggered on our view
+ * routing controller to hook into that:
+ *
+ * ```js
+ * ngModule.controller('HomePageController', ['$animate', function($animate) {
+ *   $animate.on('enter', ngViewElement, function(element) {
+ *     // the animation for this route has completed
+ *   }]);
+ * }])
+ * ```
+ *
+ * (Note that you will need to trigger a digest within the callback to get angular to notice any scope-related changes.)
+ */
+
+/**
+ * @ngdoc service
+ * @name $animate
+ * @kind object
+ *
+ * @description
+ * The ngAnimate `$animate` service documentation is the same for the core `$animate` service.
+ *
+ * Click here {@link ng.$animate to learn more about animations with `$animate`}.
+ */
+angular.module('ngAnimate', [])
+  .directive('ngAnimateSwap', ngAnimateSwapDirective)
+
+  .directive('ngAnimateChildren', $$AnimateChildrenDirective)
+  .factory('$$rAFScheduler', $$rAFSchedulerFactory)
+
+  .provider('$$animateQueue', $$AnimateQueueProvider)
+  .provider('$$animation', $$AnimationProvider)
+
+  .provider('$animateCss', $AnimateCssProvider)
+  .provider('$$animateCssDriver', $$AnimateCssDriverProvider)
+
+  .provider('$$animateJs', $$AnimateJsProvider)
+  .provider('$$animateJsDriver', $$AnimateJsDriverProvider);
+
+
+})(window, window.angular);

+ 56 - 0
www/lib/angular-animate/angular-animate.min.js

@@ -0,0 +1,56 @@
+/*
+ AngularJS v1.5.3
+ (c) 2010-2016 Google, Inc. http://angularjs.org
+ License: MIT
+*/
+(function(E,w,Va){'use strict';function ya(a,b,c){if(!a)throw Ka("areq",b||"?",c||"required");return a}function za(a,b){if(!a&&!b)return"";if(!a)return b;if(!b)return a;ba(a)&&(a=a.join(" "));ba(b)&&(b=b.join(" "));return a+" "+b}function La(a){var b={};a&&(a.to||a.from)&&(b.to=a.to,b.from=a.from);return b}function V(a,b,c){var d="";a=ba(a)?a:a&&I(a)&&a.length?a.split(/\s+/):[];q(a,function(a,f){a&&0<a.length&&(d+=0<f?" ":"",d+=c?b+a:a+b)});return d}function Ma(a){if(a instanceof H)switch(a.length){case 0:return[];
+case 1:if(1===a[0].nodeType)return a;break;default:return H(ga(a))}if(1===a.nodeType)return H(a)}function ga(a){if(!a[0])return a;for(var b=0;b<a.length;b++){var c=a[b];if(1==c.nodeType)return c}}function Na(a,b,c){q(b,function(b){a.addClass(b,c)})}function Oa(a,b,c){q(b,function(b){a.removeClass(b,c)})}function Q(a){return function(b,c){c.addClass&&(Na(a,b,c.addClass),c.addClass=null);c.removeClass&&(Oa(a,b,c.removeClass),c.removeClass=null)}}function oa(a){a=a||{};if(!a.$$prepared){var b=a.domOperation||
+O;a.domOperation=function(){a.$$domOperationFired=!0;b();b=O};a.$$prepared=!0}return a}function ha(a,b){Aa(a,b);Ba(a,b)}function Aa(a,b){b.from&&(a.css(b.from),b.from=null)}function Ba(a,b){b.to&&(a.css(b.to),b.to=null)}function T(a,b,c){var d=b.options||{};c=c.options||{};var e=(d.addClass||"")+" "+(c.addClass||""),f=(d.removeClass||"")+" "+(c.removeClass||"");a=Pa(a.attr("class"),e,f);c.preparationClasses&&(d.preparationClasses=W(c.preparationClasses,d.preparationClasses),delete c.preparationClasses);
+e=d.domOperation!==O?d.domOperation:null;Ca(d,c);e&&(d.domOperation=e);d.addClass=a.addClass?a.addClass:null;d.removeClass=a.removeClass?a.removeClass:null;b.addClass=d.addClass;b.removeClass=d.removeClass;return d}function Pa(a,b,c){function d(a){I(a)&&(a=a.split(" "));var b={};q(a,function(a){a.length&&(b[a]=!0)});return b}var e={};a=d(a);b=d(b);q(b,function(a,b){e[b]=1});c=d(c);q(c,function(a,b){e[b]=1===e[b]?null:-1});var f={addClass:"",removeClass:""};q(e,function(b,c){var d,e;1===b?(d="addClass",
+e=!a[c]):-1===b&&(d="removeClass",e=a[c]);e&&(f[d].length&&(f[d]+=" "),f[d]+=c)});return f}function A(a){return a instanceof w.element?a[0]:a}function Qa(a,b,c){var d="";b&&(d=V(b,"ng-",!0));c.addClass&&(d=W(d,V(c.addClass,"-add")));c.removeClass&&(d=W(d,V(c.removeClass,"-remove")));d.length&&(c.preparationClasses=d,a.addClass(d))}function pa(a,b){var c=b?"-"+b+"s":"";la(a,[ma,c]);return[ma,c]}function ra(a,b){var c=b?"paused":"",d=X+"PlayState";la(a,[d,c]);return[d,c]}function la(a,b){a.style[b[0]]=
+b[1]}function W(a,b){return a?b?a+" "+b:a:b}function Da(a,b,c){var d=Object.create(null),e=a.getComputedStyle(b)||{};q(c,function(a,b){var c=e[a];if(c){var F=c.charAt(0);if("-"===F||"+"===F||0<=F)c=Ra(c);0===c&&(c=null);d[b]=c}});return d}function Ra(a){var b=0;a=a.split(/\s*,\s*/);q(a,function(a){"s"==a.charAt(a.length-1)&&(a=a.substring(0,a.length-1));a=parseFloat(a)||0;b=b?Math.max(a,b):a});return b}function sa(a){return 0===a||null!=a}function Ea(a,b){var c=R,d=a+"s";b?c+="Duration":d+=" linear all";
+return[c,d]}function Fa(){var a=Object.create(null);return{flush:function(){a=Object.create(null)},count:function(b){return(b=a[b])?b.total:0},get:function(b){return(b=a[b])&&b.value},put:function(b,c){a[b]?a[b].total++:a[b]={total:1,value:c}}}}function Ga(a,b,c){q(c,function(c){a[c]=Y(a[c])?a[c]:b.style.getPropertyValue(c)})}var O=w.noop,Ha=w.copy,Ca=w.extend,H=w.element,q=w.forEach,ba=w.isArray,I=w.isString,ca=w.isObject,N=w.isUndefined,Y=w.isDefined,Ia=w.isFunction,ta=w.isElement,R,ua,X,va;N(E.ontransitionend)&&
+Y(E.onwebkittransitionend)?(R="WebkitTransition",ua="webkitTransitionEnd transitionend"):(R="transition",ua="transitionend");N(E.onanimationend)&&Y(E.onwebkitanimationend)?(X="WebkitAnimation",va="webkitAnimationEnd animationend"):(X="animation",va="animationend");var qa=X+"Delay",wa=X+"Duration",ma=R+"Delay";E=R+"Duration";var Ka=w.$$minErr("ng"),Sa={transitionDuration:E,transitionDelay:ma,transitionProperty:R+"Property",animationDuration:wa,animationDelay:qa,animationIterationCount:X+"IterationCount"},
+Ta={transitionDuration:E,transitionDelay:ma,animationDuration:wa,animationDelay:qa};w.module("ngAnimate",[]).directive("ngAnimateSwap",["$animate","$rootScope",function(a,b){return{restrict:"A",transclude:"element",terminal:!0,priority:600,link:function(b,d,e,f,r){var x,F;b.$watchCollection(e.ngAnimateSwap||e["for"],function(e){x&&a.leave(x);F&&(F.$destroy(),F=null);if(e||0===e)F=b.$new(),r(F,function(b){x=b;a.enter(b,null,d)})})}}}]).directive("ngAnimateChildren",["$interpolate",function(a){return{link:function(b,
+c,d){function e(a){c.data("$$ngAnimateChildren","on"===a||"true"===a)}var f=d.ngAnimateChildren;w.isString(f)&&0===f.length?c.data("$$ngAnimateChildren",!0):(e(a(f)(b)),d.$observe("ngAnimateChildren",e))}}}]).factory("$$rAFScheduler",["$$rAF",function(a){function b(a){d=d.concat(a);c()}function c(){if(d.length){for(var b=d.shift(),r=0;r<b.length;r++)b[r]();e||a(function(){e||c()})}}var d,e;d=b.queue=[];b.waitUntilQuiet=function(b){e&&e();e=a(function(){e=null;b();c()})};return b}]).provider("$$animateQueue",
+["$animateProvider",function(a){function b(a){if(!a)return null;a=a.split(" ");var b=Object.create(null);q(a,function(a){b[a]=!0});return b}function c(a,c){if(a&&c){var d=b(c);return a.split(" ").some(function(a){return d[a]})}}function d(a,b,c,d){return f[a].some(function(a){return a(b,c,d)})}function e(a,b){var c=0<(a.addClass||"").length,d=0<(a.removeClass||"").length;return b?c&&d:c||d}var f=this.rules={skip:[],cancel:[],join:[]};f.join.push(function(a,b,c){return!b.structural&&e(b)});f.skip.push(function(a,
+b,c){return!b.structural&&!e(b)});f.skip.push(function(a,b,c){return"leave"==c.event&&b.structural});f.skip.push(function(a,b,c){return c.structural&&2===c.state&&!b.structural});f.cancel.push(function(a,b,c){return c.structural&&b.structural});f.cancel.push(function(a,b,c){return 2===c.state&&b.structural});f.cancel.push(function(a,b,d){if(d.structural)return!1;a=b.addClass;b=b.removeClass;var e=d.addClass;d=d.removeClass;return N(a)&&N(b)||N(e)&&N(d)?!1:c(a,d)||c(b,e)});this.$get=["$$rAF","$rootScope",
+"$rootElement","$document","$$HashMap","$$animation","$$AnimateRunner","$templateRequest","$$jqLite","$$forceReflow",function(b,c,f,u,l,w,s,M,v,h){function P(){var a=!1;return function(b){a?b():c.$$postDigest(function(){a=!0;b()})}}function y(a,b,c){var g=A(b),d=A(a),k=[];(a=G[c])&&q(a,function(a){t.call(a.node,g)?k.push(a.callback):"leave"===c&&t.call(a.node,d)&&k.push(a.callback)});return k}function k(a,g,k){function m(c,g,d,k){F(function(){var c=y(J,a,g);c.length&&b(function(){q(c,function(b){b(a,
+d,k)})})});c.progress(g,d,k)}function G(b){var c=a,g=h;g.preparationClasses&&(c.removeClass(g.preparationClasses),g.preparationClasses=null);g.activeClasses&&(c.removeClass(g.activeClasses),g.activeClasses=null);Ja(a,h);ha(a,h);h.domOperation();f.complete(!b)}var h=Ha(k),t,J;if(a=Ma(a))t=A(a),J=a.parent();var h=oa(h),f=new s,F=P();ba(h.addClass)&&(h.addClass=h.addClass.join(" "));h.addClass&&!I(h.addClass)&&(h.addClass=null);ba(h.removeClass)&&(h.removeClass=h.removeClass.join(" "));h.removeClass&&
+!I(h.removeClass)&&(h.removeClass=null);h.from&&!ca(h.from)&&(h.from=null);h.to&&!ca(h.to)&&(h.to=null);if(!t)return G(),f;k=[t.className,h.addClass,h.removeClass].join(" ");if(!Ua(k))return G(),f;var v=0<=["enter","move","leave"].indexOf(g),l=!K||u[0].hidden||C.get(t);k=!l&&z.get(t)||{};var Z=!!k.state;l||Z&&1==k.state||(l=!n(a,J,g));if(l)return G(),f;v&&xa(a);l={structural:v,element:a,event:g,addClass:h.addClass,removeClass:h.removeClass,close:G,options:h,runner:f};if(Z){if(d("skip",a,l,k)){if(2===
+k.state)return G(),f;T(a,k,l);return k.runner}if(d("cancel",a,l,k))if(2===k.state)k.runner.end();else if(k.structural)k.close();else return T(a,k,l),k.runner;else if(d("join",a,l,k))if(2===k.state)T(a,l,{});else return Qa(a,v?g:null,h),g=l.event=k.event,h=T(a,k,l),k.runner}else T(a,l,{});(Z=l.structural)||(Z="animate"===l.event&&0<Object.keys(l.options.to||{}).length||e(l));if(!Z)return G(),ka(a),f;var M=(k.counter||0)+1;l.counter=M;D(a,1,l);c.$$postDigest(function(){var b=z.get(t),c=!b,b=b||{},d=
+0<(a.parent()||[]).length&&("animate"===b.event||b.structural||e(b));if(c||b.counter!==M||!d){c&&(Ja(a,h),ha(a,h));if(c||v&&b.event!==g)h.domOperation(),f.end();d||ka(a)}else g=!b.structural&&e(b,!0)?"setClass":b.event,D(a,2),b=w(a,g,b.options),b.done(function(b){G(!b);(b=z.get(t))&&b.counter===M&&ka(A(a));m(f,g,"close",{})}),f.setHost(b),m(f,g,"start",{})});return f}function xa(a){a=A(a).querySelectorAll("[data-ng-animate]");q(a,function(a){var b=parseInt(a.getAttribute("data-ng-animate")),c=z.get(a);
+if(c)switch(b){case 2:c.runner.end();case 1:z.remove(a)}})}function ka(a){a=A(a);a.removeAttribute("data-ng-animate");z.remove(a)}function J(a,b){return A(a)===A(b)}function n(a,b,c){c=H(u[0].body);var g=J(a,c)||"HTML"===a[0].nodeName,d=J(a,f),k=!1,h,m=C.get(A(a));(a=H.data(a[0],"$ngAnimatePin"))&&(b=a);for(b=A(b);b;){d||(d=J(b,f));if(1!==b.nodeType)break;a=z.get(b)||{};if(!k){var e=C.get(b);if(!0===e&&!1!==m){m=!0;break}else!1===e&&(m=!1);k=a.structural}if(N(h)||!0===h)a=H.data(b,"$$ngAnimateChildren"),
+Y(a)&&(h=a);if(k&&!1===h)break;g||(g=J(b,c));if(g&&d)break;if(!d&&(a=H.data(b,"$ngAnimatePin"))){b=A(a);continue}b=b.parentNode}return(!k||h)&&!0!==m&&d&&g}function D(a,b,c){c=c||{};c.state=b;a=A(a);a.setAttribute("data-ng-animate",b);c=(b=z.get(a))?Ca(b,c):c;z.put(a,c)}var z=new l,C=new l,K=null,g=c.$watch(function(){return 0===M.totalPendingRequests},function(a){a&&(g(),c.$$postDigest(function(){c.$$postDigest(function(){null===K&&(K=!0)})}))}),G={},m=a.classNameFilter(),Ua=m?function(a){return m.test(a)}:
+function(){return!0},Ja=Q(v),t=Node.prototype.contains||function(a){return this===a||!!(this.compareDocumentPosition(a)&16)},Z={on:function(a,b,c){var g=ga(b);G[a]=G[a]||[];G[a].push({node:g,callback:c});H(b).on("$destroy",function(){Z.off(a,b,c)})},off:function(a,b,c){function g(a,b,c){var d=ga(b);return a.filter(function(a){return!(a.node===d&&(!c||a.callback===c))})}var d=G[a];d&&(G[a]=1===arguments.length?null:g(d,b,c))},pin:function(a,b){ya(ta(a),"element","not an element");ya(ta(b),"parentElement",
+"not an element");a.data("$ngAnimatePin",b)},push:function(a,b,c,g){c=c||{};c.domOperation=g;return k(a,b,c)},enabled:function(a,b){var c=arguments.length;if(0===c)b=!!K;else if(ta(a)){var g=A(a),d=C.get(g);1===c?b=!d:C.put(g,!b)}else b=K=!!a;return b}};return Z}]}]).provider("$$animation",["$animateProvider",function(a){function b(a){return a.data("$$animationRunner")}var c=this.drivers=[];this.$get=["$$jqLite","$rootScope","$injector","$$AnimateRunner","$$HashMap","$$rAFScheduler",function(a,e,
+f,r,x,F){function u(a){function b(a){if(a.processed)return a;a.processed=!0;var d=a.domNode,h=d.parentNode;e.put(d,a);for(var f;h;){if(f=e.get(h)){f.processed||(f=b(f));break}h=h.parentNode}(f||c).children.push(a);return a}var c={children:[]},d,e=new x;for(d=0;d<a.length;d++){var f=a[d];e.put(f.domNode,a[d]={domNode:f.domNode,fn:f.fn,children:[]})}for(d=0;d<a.length;d++)b(a[d]);return function(a){var b=[],c=[],d;for(d=0;d<a.children.length;d++)c.push(a.children[d]);a=c.length;var h=0,e=[];for(d=0;d<
+c.length;d++){var f=c[d];0>=a&&(a=h,h=0,b.push(e),e=[]);e.push(f.fn);f.children.forEach(function(a){h++;c.push(a)});a--}e.length&&b.push(e);return b}(c)}var l=[],w=Q(a);return function(s,x,v){function h(a){a=a.hasAttribute("ng-animate-ref")?[a]:a.querySelectorAll("[ng-animate-ref]");var b=[];q(a,function(a){var c=a.getAttribute("ng-animate-ref");c&&c.length&&b.push(a)});return b}function P(a){var b=[],c={};q(a,function(a,g){var d=A(a.element),k=0<=["enter","move"].indexOf(a.event),d=a.structural?
+h(d):[];if(d.length){var e=k?"to":"from";q(d,function(a){var b=a.getAttribute("ng-animate-ref");c[b]=c[b]||{};c[b][e]={animationID:g,element:H(a)}})}else b.push(a)});var d={},k={};q(c,function(c,h){var e=c.from,f=c.to;if(e&&f){var m=a[e.animationID],C=a[f.animationID],n=e.animationID.toString();if(!k[n]){var D=k[n]={structural:!0,beforeStart:function(){m.beforeStart();C.beforeStart()},close:function(){m.close();C.close()},classes:y(m.classes,C.classes),from:m,to:C,anchors:[]};D.classes.length?b.push(D):
+(b.push(m),b.push(C))}k[n].anchors.push({out:e.element,"in":f.element})}else e=e?e.animationID:f.animationID,f=e.toString(),d[f]||(d[f]=!0,b.push(a[e]))});return b}function y(a,b){a=a.split(" ");b=b.split(" ");for(var c=[],d=0;d<a.length;d++){var k=a[d];if("ng-"!==k.substring(0,3))for(var e=0;e<b.length;e++)if(k===b[e]){c.push(k);break}}return c.join(" ")}function k(a){for(var b=c.length-1;0<=b;b--){var d=c[b];if(f.has(d)&&(d=f.get(d)(a)))return d}}function xa(a,c){a.from&&a.to?(b(a.from.element).setHost(c),
+b(a.to.element).setHost(c)):b(a.element).setHost(c)}function ka(){var a=b(s);!a||"leave"===x&&v.$$domOperationFired||a.end()}function J(b){s.off("$destroy",ka);s.removeData("$$animationRunner");w(s,v);ha(s,v);v.domOperation();C&&a.removeClass(s,C);s.removeClass("ng-animate");D.complete(!b)}v=oa(v);var n=0<=["enter","move","leave"].indexOf(x),D=new r({end:function(){J()},cancel:function(){J(!0)}});if(!c.length)return J(),D;s.data("$$animationRunner",D);var z=za(s.attr("class"),za(v.addClass,v.removeClass)),
+C=v.tempClasses;C&&(z+=" "+C,v.tempClasses=null);var K;n&&(K="ng-"+x+"-prepare",a.addClass(s,K));l.push({element:s,classes:z,event:x,structural:n,options:v,beforeStart:function(){s.addClass("ng-animate");C&&a.addClass(s,C);K&&(a.removeClass(s,K),K=null)},close:J});s.on("$destroy",ka);if(1<l.length)return D;e.$$postDigest(function(){var a=[];q(l,function(c){b(c.element)?a.push(c):c.close()});l.length=0;var c=P(a),d=[];q(c,function(a){d.push({domNode:A(a.from?a.from.element:a.element),fn:function(){a.beforeStart();
+var c,d=a.close;if(b(a.anchors?a.from.element||a.to.element:a.element)){var g=k(a);g&&(c=g.start)}c?(c=c(),c.done(function(a){d(!a)}),xa(a,c)):d()}})});F(u(d))});return D}}]}]).provider("$animateCss",["$animateProvider",function(a){var b=Fa(),c=Fa();this.$get=["$window","$$jqLite","$$AnimateRunner","$timeout","$$forceReflow","$sniffer","$$rAFScheduler","$$animateQueue",function(a,e,f,r,x,F,u,l){function w(a,b){var c=a.parentNode;return(c.$$ngAnimateParentKey||(c.$$ngAnimateParentKey=++P))+"-"+a.getAttribute("class")+
+"-"+b}function s(k,h,f,l){var n;0<b.count(f)&&(n=c.get(f),n||(h=V(h,"-stagger"),e.addClass(k,h),n=Da(a,k,l),n.animationDuration=Math.max(n.animationDuration,0),n.transitionDuration=Math.max(n.transitionDuration,0),e.removeClass(k,h),c.put(f,n)));return n||{}}function M(a){y.push(a);u.waitUntilQuiet(function(){b.flush();c.flush();for(var a=x(),d=0;d<y.length;d++)y[d](a);y.length=0})}function v(c,h,e){h=b.get(e);h||(h=Da(a,c,Sa),"infinite"===h.animationIterationCount&&(h.animationIterationCount=1));
+b.put(e,h);c=h;e=c.animationDelay;h=c.transitionDelay;c.maxDelay=e&&h?Math.max(e,h):e||h;c.maxDuration=Math.max(c.animationDuration*c.animationIterationCount,c.transitionDuration);return c}var h=Q(e),P=0,y=[];return function(a,c){function d(){n()}function u(){n(!0)}function n(b){if(!(P||H&&da)){P=!0;da=!1;g.$$skipPreparationClasses||e.removeClass(a,fa);e.removeClass(a,ga);ra(m,!1);pa(m,!1);q(y,function(a){m.style[a[0]]=""});h(a,g);ha(a,g);Object.keys(G).length&&q(G,function(a,b){a?m.style.setProperty(b,
+a):m.style.removeProperty(b)});if(g.onDone)g.onDone();ea&&ea.length&&a.off(ea.join(" "),C);var c=a.data("$$animateCss");c&&(r.cancel(c[0].timer),a.removeData("$$animateCss"));E&&E.complete(!b)}}function D(a){p.blockTransition&&pa(m,a);p.blockKeyframeAnimation&&ra(m,!!a)}function z(){E=new f({end:d,cancel:u});M(O);n();return{$$willAnimate:!1,start:function(){return E},end:d}}function C(a){a.stopPropagation();var b=a.originalEvent||a;a=b.$manualTimeStamp||Date.now();b=parseFloat(b.elapsedTime.toFixed(3));
+Math.max(a-W,0)>=Q&&b>=L&&(H=!0,n())}function K(){function b(){if(!P){D(!1);q(y,function(a){m.style[a[0]]=a[1]});h(a,g);e.addClass(a,ga);if(p.recalculateTimingStyles){na=m.className+" "+fa;ia=w(m,na);B=v(m,na,ia);$=B.maxDelay;I=Math.max($,0);L=B.maxDuration;if(0===L){n();return}p.hasTransitions=0<B.transitionDuration;p.hasAnimations=0<B.animationDuration}p.applyAnimationDelay&&($="boolean"!==typeof g.delay&&sa(g.delay)?parseFloat(g.delay):$,I=Math.max($,0),B.animationDelay=$,aa=[qa,$+"s"],y.push(aa),
+m.style[aa[0]]=aa[1]);Q=1E3*I;T=1E3*L;if(g.easing){var d,f=g.easing;p.hasTransitions&&(d=R+"TimingFunction",y.push([d,f]),m.style[d]=f);p.hasAnimations&&(d=X+"TimingFunction",y.push([d,f]),m.style[d]=f)}B.transitionDuration&&ea.push(ua);B.animationDuration&&ea.push(va);W=Date.now();var l=Q+1.5*T;d=W+l;var f=a.data("$$animateCss")||[],K=!0;if(f.length){var z=f[0];(K=d>z.expectedEndTime)?r.cancel(z.timer):f.push(n)}K&&(l=r(c,l,!1),f[0]={timer:l,expectedEndTime:d},f.push(n),a.data("$$animateCss",f));
+if(ea.length)a.on(ea.join(" "),C);g.to&&(g.cleanupStyles&&Ga(G,m,Object.keys(g.to)),Ba(a,g))}}function c(){var b=a.data("$$animateCss");if(b){for(var d=1;d<b.length;d++)b[d]();a.removeData("$$animateCss")}}if(!P)if(m.parentNode){var d=function(a){if(H)da&&a&&(da=!1,n());else if(da=!a,B.animationDuration)if(a=ra(m,da),da)y.push(a);else{var b=y,c=b.indexOf(a);0<=a&&b.splice(c,1)}},f=0<ca&&(B.transitionDuration&&0===U.transitionDuration||B.animationDuration&&0===U.animationDuration)&&Math.max(U.animationDelay,
+U.transitionDelay);f?r(b,Math.floor(f*ca*1E3),!1):b();N.resume=function(){d(!0)};N.pause=function(){d(!1)}}else n()}var g=c||{};g.$$prepared||(g=oa(Ha(g)));var G={},m=A(a);if(!m||!m.parentNode||!l.enabled())return z();var y=[],x=a.attr("class"),t=La(g),P,da,H,E,N,I,Q,L,T,W,ea=[];if(0===g.duration||!F.animations&&!F.transitions)return z();var ja=g.event&&ba(g.event)?g.event.join(" "):g.event,Y="",S="";ja&&g.structural?Y=V(ja,"ng-",!0):ja&&(Y=ja);g.addClass&&(S+=V(g.addClass,"-add"));g.removeClass&&
+(S.length&&(S+=" "),S+=V(g.removeClass,"-remove"));g.applyClassesEarly&&S.length&&h(a,g);var fa=[Y,S].join(" ").trim(),na=x+" "+fa,ga=V(fa,"-active"),x=t.to&&0<Object.keys(t.to).length;if(!(0<(g.keyframeStyle||"").length||x||fa))return z();var ia,U;0<g.stagger?(t=parseFloat(g.stagger),U={transitionDelay:t,animationDelay:t,transitionDuration:0,animationDuration:0}):(ia=w(m,na),U=s(m,fa,ia,Ta));g.$$skipPreparationClasses||e.addClass(a,fa);g.transitionStyle&&(t=[R,g.transitionStyle],la(m,t),y.push(t));
+0<=g.duration&&(t=0<m.style[R].length,t=Ea(g.duration,t),la(m,t),y.push(t));g.keyframeStyle&&(t=[X,g.keyframeStyle],la(m,t),y.push(t));var ca=U?0<=g.staggerIndex?g.staggerIndex:b.count(ia):0;(ja=0===ca)&&!g.skipBlocking&&pa(m,9999);var B=v(m,na,ia),$=B.maxDelay;I=Math.max($,0);L=B.maxDuration;var p={};p.hasTransitions=0<B.transitionDuration;p.hasAnimations=0<B.animationDuration;p.hasTransitionAll=p.hasTransitions&&"all"==B.transitionProperty;p.applyTransitionDuration=x&&(p.hasTransitions&&!p.hasTransitionAll||
+p.hasAnimations&&!p.hasTransitions);p.applyAnimationDuration=g.duration&&p.hasAnimations;p.applyTransitionDelay=sa(g.delay)&&(p.applyTransitionDuration||p.hasTransitions);p.applyAnimationDelay=sa(g.delay)&&p.hasAnimations;p.recalculateTimingStyles=0<S.length;if(p.applyTransitionDuration||p.applyAnimationDuration)L=g.duration?parseFloat(g.duration):L,p.applyTransitionDuration&&(p.hasTransitions=!0,B.transitionDuration=L,t=0<m.style[R+"Property"].length,y.push(Ea(L,t))),p.applyAnimationDuration&&(p.hasAnimations=
+!0,B.animationDuration=L,y.push([wa,L+"s"]));if(0===L&&!p.recalculateTimingStyles)return z();if(null!=g.delay){var aa;"boolean"!==typeof g.delay&&(aa=parseFloat(g.delay),I=Math.max(aa,0));p.applyTransitionDelay&&y.push([ma,aa+"s"]);p.applyAnimationDelay&&y.push([qa,aa+"s"])}null==g.duration&&0<B.transitionDuration&&(p.recalculateTimingStyles=p.recalculateTimingStyles||ja);Q=1E3*I;T=1E3*L;g.skipBlocking||(p.blockTransition=0<B.transitionDuration,p.blockKeyframeAnimation=0<B.animationDuration&&0<U.animationDelay&&
+0===U.animationDuration);g.from&&(g.cleanupStyles&&Ga(G,m,Object.keys(g.from)),Aa(a,g));p.blockTransition||p.blockKeyframeAnimation?D(L):g.skipBlocking||pa(m,!1);return{$$willAnimate:!0,end:d,start:function(){if(!P)return N={end:d,cancel:u,resume:null,pause:null},E=new f(N),M(K),E}}}}]}]).provider("$$animateCssDriver",["$$animationProvider",function(a){a.drivers.push("$$animateCssDriver");this.$get=["$animateCss","$rootScope","$$AnimateRunner","$rootElement","$sniffer","$$jqLite","$document",function(a,
+c,d,e,f,r,x){function F(a){return a.replace(/\bng-\S+\b/g,"")}function u(a,b){I(a)&&(a=a.split(" "));I(b)&&(b=b.split(" "));return a.filter(function(a){return-1===b.indexOf(a)}).join(" ")}function l(c,e,f){function k(a){var b={},c=A(a).getBoundingClientRect();q(["width","height","top","left"],function(a){var d=c[a];switch(a){case "top":d+=M.scrollTop;break;case "left":d+=M.scrollLeft}b[a]=Math.floor(d)+"px"});return b}function l(){var c=F(f.attr("class")||""),d=u(c,n),c=u(n,c),d=a(r,{to:k(f),addClass:"ng-anchor-in "+
+d,removeClass:"ng-anchor-out "+c,delay:!0});return d.$$willAnimate?d:null}function x(){r.remove();e.removeClass("ng-animate-shim");f.removeClass("ng-animate-shim")}var r=H(A(e).cloneNode(!0)),n=F(r.attr("class")||"");e.addClass("ng-animate-shim");f.addClass("ng-animate-shim");r.addClass("ng-anchor");v.append(r);var D;c=function(){var c=a(r,{addClass:"ng-anchor-out",delay:!0,from:k(e)});return c.$$willAnimate?c:null}();if(!c&&(D=l(),!D))return x();var z=c||D;return{start:function(){function a(){c&&
+c.end()}var b,c=z.start();c.done(function(){c=null;if(!D&&(D=l()))return c=D.start(),c.done(function(){c=null;x();b.complete()}),c;x();b.complete()});return b=new d({end:a,cancel:a})}}}function w(a,b,c,e){var f=s(a,O),r=s(b,O),x=[];q(e,function(a){(a=l(c,a.out,a["in"]))&&x.push(a)});if(f||r||0!==x.length)return{start:function(){function a(){q(b,function(a){a.end()})}var b=[];f&&b.push(f.start());r&&b.push(r.start());q(x,function(a){b.push(a.start())});var c=new d({end:a,cancel:a});d.all(b,function(a){c.complete(a)});
+return c}}}function s(c){var d=c.element,e=c.options||{};c.structural&&(e.event=c.event,e.structural=!0,e.applyClassesEarly=!0,"leave"===c.event&&(e.onDone=e.domOperation));e.preparationClasses&&(e.event=W(e.event,e.preparationClasses));c=a(d,e);return c.$$willAnimate?c:null}if(!f.animations&&!f.transitions)return O;var M=x[0].body;c=A(e);var v=H(c.parentNode&&11===c.parentNode.nodeType||M.contains(c)?c:M);Q(r);return function(a){return a.from&&a.to?w(a.from,a.to,a.classes,a.anchors):s(a)}}]}]).provider("$$animateJs",
+["$animateProvider",function(a){this.$get=["$injector","$$AnimateRunner","$$jqLite",function(b,c,d){function e(c){c=ba(c)?c:c.split(" ");for(var d=[],e={},f=0;f<c.length;f++){var l=c[f],q=a.$$registeredAnimations[l];q&&!e[l]&&(d.push(b.get(q)),e[l]=!0)}return d}var f=Q(d);return function(a,b,d,u){function l(){u.domOperation();f(a,u)}function w(a,b,d,e,g){switch(d){case "animate":b=[b,e.from,e.to,g];break;case "setClass":b=[b,h,H,g];break;case "addClass":b=[b,h,g];break;case "removeClass":b=[b,H,g];
+break;default:b=[b,g]}b.push(e);if(a=a.apply(a,b))if(Ia(a.start)&&(a=a.start()),a instanceof c)a.done(g);else if(Ia(a))return a;return O}function s(a,b,d,e,g){var f=[];q(e,function(e){var h=e[g];h&&f.push(function(){var e,g,f=!1,k=function(a){f||(f=!0,(g||O)(a),e.complete(!a))};e=new c({end:function(){k()},cancel:function(){k(!0)}});g=w(h,a,b,d,function(a){k(!1===a)});return e})});return f}function A(a,b,d,e,g){var f=s(a,b,d,e,g);if(0===f.length){var h,k;"beforeSetClass"===g?(h=s(a,"removeClass",
+d,e,"beforeRemoveClass"),k=s(a,"addClass",d,e,"beforeAddClass")):"setClass"===g&&(h=s(a,"removeClass",d,e,"removeClass"),k=s(a,"addClass",d,e,"addClass"));h&&(f=f.concat(h));k&&(f=f.concat(k))}if(0!==f.length)return function(a){var b=[];f.length&&q(f,function(a){b.push(a())});b.length?c.all(b,a):a();return function(a){q(b,function(b){a?b.cancel():b.end()})}}}var v=!1;3===arguments.length&&ca(d)&&(u=d,d=null);u=oa(u);d||(d=a.attr("class")||"",u.addClass&&(d+=" "+u.addClass),u.removeClass&&(d+=" "+
+u.removeClass));var h=u.addClass,H=u.removeClass,y=e(d),k,E;if(y.length){var I,J;"leave"==b?(J="leave",I="afterLeave"):(J="before"+b.charAt(0).toUpperCase()+b.substr(1),I=b);"enter"!==b&&"move"!==b&&(k=A(a,b,u,y,J));E=A(a,b,u,y,I)}if(k||E){var n;return{$$willAnimate:!0,end:function(){n?n.end():(v=!0,l(),ha(a,u),n=new c,n.complete(!0));return n},start:function(){function b(c){v=!0;l();ha(a,u);n.complete(c)}if(n)return n;n=new c;var d,e=[];k&&e.push(function(a){d=k(a)});e.length?e.push(function(a){l();
+a(!0)}):l();E&&e.push(function(a){d=E(a)});n.setHost({end:function(){v||((d||O)(void 0),b(void 0))},cancel:function(){v||((d||O)(!0),b(!0))}});c.chain(e,b);return n}}}}}]}]).provider("$$animateJsDriver",["$$animationProvider",function(a){a.drivers.push("$$animateJsDriver");this.$get=["$$animateJs","$$AnimateRunner",function(a,c){function d(c){return a(c.element,c.event,c.classes,c.options)}return function(a){if(a.from&&a.to){var b=d(a.from),r=d(a.to);if(b||r)return{start:function(){function a(){return function(){q(d,
+function(a){a.end()})}}var d=[];b&&d.push(b.start());r&&d.push(r.start());c.all(d,function(a){e.complete(a)});var e=new c({end:a(),cancel:a()});return e}}}else return d(a)}}]}])})(window,window.angular);
+//# sourceMappingURL=angular-animate.min.js.map

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 4 - 0
www/lib/angular-animate/angular-animate.min.js.map


+ 10 - 0
www/lib/angular-animate/bower.json

@@ -0,0 +1,10 @@
+{
+  "name": "angular-animate",
+  "version": "1.5.3",
+  "license": "MIT",
+  "main": "./angular-animate.js",
+  "ignore": [],
+  "dependencies": {
+    "angular": "1.5.3"
+  }
+}

+ 2 - 0
www/lib/angular-animate/index.js

@@ -0,0 +1,2 @@
+require('./angular-animate');
+module.exports = 'ngAnimate';

+ 26 - 0
www/lib/angular-animate/package.json

@@ -0,0 +1,26 @@
+{
+  "name": "angular-animate",
+  "version": "1.5.3",
+  "description": "AngularJS module for animations",
+  "main": "index.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/angular/angular.js.git"
+  },
+  "keywords": [
+    "angular",
+    "framework",
+    "browser",
+    "animation",
+    "client-side"
+  ],
+  "author": "Angular Core Team <angular-core+npm@google.com>",
+  "license": "MIT",
+  "bugs": {
+    "url": "https://github.com/angular/angular.js/issues"
+  },
+  "homepage": "http://angularjs.org"
+}

+ 15 - 0
www/lib/squel/.bower.json

@@ -0,0 +1,15 @@
+{
+  "name": "squel",
+  "homepage": "https://github.com/hiddentao/squel",
+  "version": "5.3.3",
+  "_release": "5.3.3",
+  "_resolution": {
+    "type": "version",
+    "tag": "5.3.3",
+    "commit": "6cf684a998795a29a8a10124fd9fc6e15282ac72"
+  },
+  "_source": "https://github.com/hiddentao/squel.git",
+  "_target": "^5.3.3",
+  "_originalSource": "squel",
+  "_direct": true
+}

+ 5 - 0
www/lib/squel/.gitignore

@@ -0,0 +1,5 @@
+*.iml
+.idea/
+node_modules/
+.sass-cache/
+docs/

+ 4 - 0
www/lib/squel/.npmignore

@@ -0,0 +1,4 @@
+test
+docs
+src
+.*

+ 20 - 0
www/lib/squel/.travis.yml

@@ -0,0 +1,20 @@
+language: node_js
+
+node_js:
+  - "0.12"
+  - "4"
+  - "5"
+
+branches:
+  only:
+    - master
+
+before_script:
+  - "npm install"
+
+script:
+  - "npm test"
+
+notifications:
+  email:
+    - ram@hiddentao.com

+ 171 - 0
www/lib/squel/CHANGELOG.md

@@ -0,0 +1,171 @@
+# Changelog for [squel](https://github.com/hiddentao/squel)
+
+## 12 Jul 2016 (5.3.3)
+* #249 - Postgres `DISTINCT ON` clause
+
+## 13 Jun 2016 (5.3.2)
+* #234 - Fix handling of expression field names
+
+## 5 Jun 2016 (5.3.1)
+* #158, #239 - Support for CTE queries (`WITH` clause)
+* #242 - Fix auto-quoting table names
+* Removed bower.json
+
+## 18 May 2016 (5.2.1)
+* Re-fix for #109 - custom string formatting wasn't quite working
+
+## 18 May 2016 (5.2.0)
+* Fix for #109 - custom string formatting function enabled
+* Fix for #235 - fix a regression
+
+## 14 May 2016 (5.1.0)
+* Fix for #231 - try not to add extra brackets
+* Fix for #233 - ability to specify target table in `DELETE` queries
+
+## 17 Apr 2016 (5.0.4)
+* Fix for #227 - MSSQL insert without fields fails
+
+## 13 Apr 2016 (5.0.3)
+* Fix for #225 - auto-quote field names had stopped working
+
+## 11 Apr 2016 (5.0.2)
+* Fix for #226 - empty expressions in where clause
+
+## 6 Apr 2016 (5.0.1)
+* Fix for #223 - array looping should not use `for-in`
+
+## 29 Mar 2016 (5.0.0)
+* Complete architectural rewrite - see #201
+
+## 23 Mar 2016 (4.4.2)
+* Fix for #220 and #221 and other similar issues
+
+## 20 Mar 2016 (4.4.1)
+* Fixed for #219
+
+## 19 Mar 2016 (4.4.0)
+* Ported coffeescript to ES6
+
+## 29 Feb 2016 (4.3.3)
+* Fix for #216
+
+## 24 Feb 2016 (4.3.2)
+* Fix for #210
+
+## 18 Feb 2016 (4.3.1)
+* #208 - Rework expressions to allow for easier cloning.
+
+## 18 Feb 2016 (4.3.0)
+* #207 - Added `CASE` clauses and `useAsForTableAliasNames` option.
+
+## 17 Feb 2016 (4.2.4)
+* #199 - Added `FROM` to `UPDATE` for postgres flavour
+
+## 20 Jan 2016 (4.2.3)
+* Placeholder parameter character is now configurable
+* Guide docs now print results below code
+* Re-instituted CHANGELOG.md
+* Can now get current flavour of Squel using `flavour` prop
+
+## 13 Nov 2015 (4.2.2)
+* Merged #191
+
+## 30 Aug 2014 (3.8.1)
+* #90 - custom value handlers with primitives
+* #87 - OrderBlock not compatible by values
+
+## 11 Aug 2014 (3.7.0)
+* #76 - MSSQL flavour
+* #85 - Using expressions in .where() followed by .toParam()
+
+## 30 July 2014 (3.6.1)
+* Better fix for #82
+* Treat `*` as a special case when auto-quoting field names
+* Fix for #84
+
+## 19 July 2014 (3.5.0)
+* #82 - `ON DUPLIATE KEY UPDATE` enchancements
+* #25, #72, #73 - parameter substitution in expressions
+* #79 - smarter automatic fieldname quoting
+* #75 - disable automatic string quoting on a per-field basis
+* #55 - specify sub-query as a field
+* #80, #81 - Bugfixes
+
+## 17 May 2014 (3.4.1)
+* #62 - can specify query separator string
+
+## 15 May 2014 (3.3.0)
+* Shifted `replaceSingleQuotes` and related option into Squel core.
+
+## 9 May 2014 (3.2.0)
+* Added DELETE..RETURNING for Postgres (#60)
+* Auto-generate version string (#61)
+* Don't commit docs/ folder anymore. Also don't auto-build docs as part of build.
+
+## 21 Mar 2014 (3.1.1)
+* Don't format parameter values returned from the toParam() call, unless their custom value types (#54)
+
+## 20 Mar 2014 (3.0.1)
+* Added `setFields` and `setFieldRows` to make setting multple fields and inserting multiple rows easier (#50)
+* Removed `usingValuePlaceholders` option that was deprecated in 2.0.0
+
+## 16 Dec 2013 (2.0.0)
+* Added `RETURNING` clause to `UPDATE` queries for Postgres flavour (#42)
+* Added better support for parameterized queries (#34)
+* Added `squel.VERSION` constant
+
+
+## 7 Oct 2013 (1.2.1)
+* Added ON DUPLICATE KEY UPDATE clause for MySQL flavour (#36)
+* Added single quote replacement option for Postgres flavour (#35)
+
+
+## 2 Oct 2013 (1.2)
+* Switched from Make to Grunt
+* Added `fields()` method to SELECT builder (#29)
+* Expression trees can now be cloned (#31)
+* Added concept of SQL 'flavours' and merged in the Postgres `RETURNING` command #33
+
+
+## 10 Jun 2013 (1.1.3)
+* Table names in SELECT queries can now be queries themselves (i.e. SQL sub statements)
+
+
+## 2 Jun 2013 (1.1.2)
+* Parameterised WHERE clauses now supported.
+* Custom value types can now be handled in a special way. Global and per-instance handlers supported.
+
+
+## 27 Mar 2013 (1.1)
+* Squel can now be customized to include proprietary commands and queries.
+* AMD support added.
+
+
+## 4 Jan 2013 (1.0.6)
+* Squel can now be told to auto-quote table and field names.
+
+
+## 3 Nov 2012 (1.0.5)
+
+* DELETE queries can now contain JOINs
+* Query builder instances can be clone()'d
+* Cleaner and more thorough tests (and replaced Vows with Mocha, Sinon and Chai)
+* Fixed documentation errors
+
+
+## 17 Aug 2012 (1.0.4)
+
+* QueryBuilder base class for all query builders
+* Exporting query builders
+* Escaping strings with single quotes for PostgreSQL compatibility
+* Migrating to make
+
+
+## 27 Jan 2012 (1.0.3)
+
+* Added 'usingValuePlaceholders' option for INSERT and UPDATE query builders.
+
+
+## 20 Dec 2011 (1.0.0)
+
+* Initial version.

+ 28 - 0
www/lib/squel/CONTRIBUTING.md

@@ -0,0 +1,28 @@
+# Contribute to Squel
+
+This guide guidelines for those wishing to contribute to Squel.
+
+## Contributor license agreement
+
+By submitting code as an individual or as an entity you agree that your code is [licensed the same as Squel](README.md).
+
+## Issues and pull requests
+
+Issues and merge requests should be in English and contain appropriate language for audiences of all ages.
+
+We will only accept a merge requests which meets the following criteria:
+
+* Squel.js and squel.min.js have been rebuilt using `npm run build`.
+* Includes proper tests and all tests pass (unless it contains a test exposing a bug in existing code)
+* Can be merged without problems (if not please use: `git rebase master`)
+* Does not break any existing functionality
+* Fixes one specific issue or implements one specific feature (do not combine things, send separate merge requests if needed)
+* Keeps the Squel code base clean and well structured
+* Contains functionality we think other users will benefit from too
+* Doesn't add unnessecary configuration options since they complicate future changes
+* Update the docs in the `gh-pages` branch and in the master `README.md` if necessary
+
+
+## Release process (for core contributors)
+
+See `RELEASE.md`.

+ 27 - 0
www/lib/squel/LICENSE.md

@@ -0,0 +1,27 @@
+Copyright (c) [Ramesh Nair](http://www.hiddentao.com/)
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+
+
+
+

+ 369 - 0
www/lib/squel/README.md

@@ -0,0 +1,369 @@
+# squel - SQL query string builder
+
+[![Build Status](https://secure.travis-ci.org/hiddentao/squel.png?branch=master)](http://travis-ci.org/hiddentao/squel) [![NPM module](https://badge.fury.io/js/squel.png)](https://badge.fury.io/js/squel) [![Join the chat at https://gitter.im/hiddentao/squel](https://img.shields.io/badge/gitter-join%20chat-blue.svg)](https://gitter.im/hiddentao/squel)
+
+A flexible and powerful SQL query string builder for Javascript.
+
+Full documentation (guide and API) at [http://squeljs.org/](http://squeljs.org/).
+
+**Note: The latest Squel version only works on Node 0.12 or above. Please use Squel 4.4.1 for Node <0.12. The [old 4.x docs](http://hiddentao.github.io/squel/v4/index.html) are also still available.**
+
+## Features
+
+* Works in node.js and in the browser.
+* Supports the standard SQL queries: SELECT, UPDATE, INSERT and DELETE.
+* Supports non-standard commands for popular DB engines such as MySQL.
+* Supports paramterized queries for safe value escaping.
+* Can be customized to build any query or command of your choosing.
+* Uses method chaining for ease of use.
+* Small: ~7 KB minified and gzipped
+* And much more, [see the guide..](http://squeljs.org/)
+
+**WARNING: Do not ever pass queries generated on the client side to your web server for execution.** Such a configuration would make it trivial for a casual attacker to execute arbitrary queries&mdash;as with an SQL-injection vector, but much easier to exploit and practically impossible to protect against.
+
+## Installation
+
+Install using [npm](http://npmjs.org/):
+
+```bash
+$ npm install squel
+```
+
+## Available files
+
+* `squel.js` - unminified version of Squel with the standard commands and all available non-standard commands added
+* `squel.min.js` - minified version of `squel.js`
+* `squel-basic.js` - unminified version of Squel with only the standard SQL commands
+* `squel-basic.min.js` - minified version of `squel-basic.js`
+
+
+## Examples
+
+Before running the examples ensure you have `squel` installed and enabled at the top of your script:
+
+    var squel = require("squel");
+
+### SELECT
+
+```javascript
+// SELECT * FROM table
+squel.select()
+    .from("table")
+    .toString()
+
+// SELECT t1.id, t2.name FROM table `t1` LEFT JOIN table2 `t2` ON (t1.id = t2.id) WHERE (t2.name <> 'Mark') AND (t2.name <> 'John') GROUP BY t1.id
+squel.select()
+    .from("table", "t1")
+    .field("t1.id")
+    .field("t2.name")
+    .left_join("table2", "t2", "t1.id = t2.id")
+    .group("t1.id")
+    .where("t2.name <> 'Mark'")
+    .where("t2.name <> 'John'")
+    .toString()
+
+// SELECT `t1`.`id`, `t1`.`name` as "My name", `t1`.`started` as "Date" FROM table `t1` WHERE age IN (RANGE(1, 1.2)) ORDER BY id ASC LIMIT 20
+squel.select({ autoQuoteFieldNames: true })
+    .from("table", "t1")
+    .field("t1.id")
+    .field("t1.name", "My name")
+    .field("t1.started", "Date")
+    .where("age IN ?", squel.str('RANGE(?, ?)', 1, 1.2))
+    .order("id")
+    .limit(20)
+    .toString()
+```
+
+You can build parameterized queries:
+
+```js
+/*
+{
+    text: "SELECT `t1`.`id`, `t1`.`name` as "My name", `t1`.`started` as "Date" FROM table `t1` WHERE age IN (RANGE(?, ?)) ORDER BY id ASC LIMIT 20",
+    values: [1, 1.2]
+}
+*/
+squel.select({ autoQuoteFieldNames: true })
+    .from("table", "t1")
+    .field("t1.id")
+    .field("t1.name", "My name")
+    .field("t1.started", "Date")
+    .where("age IN ?", squel.str('RANGE(?, ?)', 1, 1.2))
+    .order("id")
+    .limit(20)
+    .toParam()
+```
+
+
+You can use nested queries:
+
+```javascript
+// SELECT s.id FROM (SELECT * FROM students) `s` INNER JOIN (SELECT id FROM marks) `m` ON (m.id = s.id)
+squel.select()
+    .from( squel.select().from('students'), 's' )
+    .field('id')
+    .join( squel.select().from('marks').field('id'), 'm', 'm.id = s.id' )
+    .toString()
+```
+
+### UPDATE
+
+```javascript
+// UPDATE test SET f1 = 1
+squel.update()
+    .table("test")
+    .set("f1", 1)
+    .toString()
+
+// UPDATE test, test2, test3 AS `a` SET test.id = 1, test2.val = 1.2, a.name = "Ram", a.email = NULL, a.count = a.count + 1
+squel.update()
+    .table("test")
+    .set("test.id", 1)
+    .table("test2")
+    .set("test2.val", 1.2)
+    .table("test3","a")
+    .setFields({
+        "a.name": "Ram",
+        "a.email": null,
+        "a.count = a.count + 1": undefined
+    })
+    .toString()
+```
+
+### INSERT
+
+```javascript
+// INSERT INTO test (f1) VALUES (1)
+squel.insert()
+    .into("test")
+    .set("f1", 1)
+    .toString()
+
+// INSERT INTO test (name, age) VALUES ('Thomas', 29), ('Jane', 31)
+squel.insert()
+    .into("test")
+    .setFieldsRows([
+        { name: "Thomas", age: 29 },
+        { name: "Jane", age: 31 }    
+    ])
+    .toString()
+```
+
+### DELETE
+
+```javascript
+// DELETE FROM test
+squel.delete()
+    .from("test")
+    .toString()
+
+// DELETE FROM table1 WHERE (table1.id = 2) ORDER BY id DESC LIMIT 2
+squel.delete()
+    .from("table1")
+    .where("table1.id = ?", 2)
+    .order("id", false)
+    .limit(2)
+```
+
+### Paramterized queries
+
+Use the `useParam()` method to obtain a parameterized query with a separate list of formatted parameter values:
+
+```javascript
+// { text: "INSERT INTO test (f1, f2, f3, f4, f5) VALUES (?, ?, ?, ?, ?)", values: [1, 1.2, "TRUE", "blah", "NULL"] }
+squel.insert()
+    .into("test")
+    .set("f1", 1)
+    .set("f2", 1.2)
+    .set("f3", true)
+    .set("f4", "blah")
+    .set("f5", null)
+    .toParam()
+```
+
+
+### Expression builder
+
+There is also an expression builder which allows you to build complex expressions for `WHERE` and `ON` clauses:
+
+```javascript
+// test = 3 OR test = 4
+squel.expr()
+    .or("test = 3")
+    .or("test = 4")
+    .toString()
+
+// test = 3 AND (inner = 1 OR inner = 2) OR (inner = 3 AND inner = 4 OR (inner IN ('str1, 'str2', NULL)))
+squel.expr()
+    .and("test = 3")
+    .and(
+        squel.expr()
+            .or("inner = 1")
+            .or("inner = 2")
+    )
+    .or(
+        squel.expr()
+            .and("inner = ?", 3)
+            .and("inner = ?", 4)
+            .or(
+                squel.expr()
+                    .and("inner IN ?", ['str1', 'str2', null])
+            )
+    )
+    .toString()
+
+// SELECT * FROM test INNER JOIN test2 ON (test.id = test2.id) WHERE (test = 3 OR test = 4)
+squel.select()
+    .join( "test2", null, squel.expr().and("test.id = test2.id") )
+    .where( squel.expr().or("test = 3").or("test = 4") )
+```
+
+### Custom value types
+
+By default Squel does not support the use of object instances as field values. Instead it lets you tell it how you want
+specific object types to be handled:
+
+```javascript
+// handler for objects of type Date
+squel.registerValueHandler(Date, function(date) {
+  return date.getFullYear() + '/' + (date.getMonth() + 1) + '/' + date.getDate();
+});
+
+squel.update().
+  .table('students')
+  .set('start_date', new Date(2013, 5, 1))
+  .toString()
+
+// UPDATE students SET start_date = '2013/6/1'
+```
+
+
+_Note that custom value handlers can be overridden on a per-instance basis (see the [docs](http://squeljs.org/))_
+
+### Custom queries
+
+Squel allows you to override the built-in query builders with your own as well as create your own types of queries:
+
+```javascript
+// ------------------------------------------------------
+// Setup the PRAGMA query builder
+// ------------------------------------------------------
+var util = require('util');   // to use util.inherits() from node.js
+
+var CommandBlock = function() {};
+util.inherits(CommandBlock, squel.cls.Block);
+
+// private method - will not get exposed within the query builder
+CommandBlock.prototype._command = function(_command) {
+  this._command = _command;
+}
+
+// public method - will get exposed within the query builder
+CommandBlock.prototype.compress = function() {
+  this._command('compress');
+};
+
+CommandBlock.prototype.buildStr = function() {
+  return this._command.toUpperCase();
+};
+
+
+// generic parameter block
+var ParamBlock = function() {};
+util.inherits(ParamBlock, squel.cls.Block);
+
+ParamBlock.prototype.param = function(p) {
+  this._p = p;
+};
+
+ParamBlock.prototype.buildStr = function() {
+  return this._p;
+};
+
+
+// pragma query builder
+var PragmaQuery = function(options) {
+  squel.cls.QueryBuilder.call(this, options, [
+      new squel.cls.StringBlock(options, 'PRAGMA'),
+      new CommandBlock(),
+      new ParamBlock()
+  ]);
+};
+util.inherits(PragmaQuery, squel.cls.QueryBuilder);
+
+
+// convenience method (we can override built-in squel methods this way too)
+squel.pragma = function(options) {
+  return new PragmaQuery(options)
+};
+
+
+// ------------------------------------------------------
+// Build a PRAGMA query
+// ------------------------------------------------------
+
+squel.pragma()
+  .compress()
+  .param('test')
+  .toString();
+
+// 'PRAGMA COMPRESS test'
+```
+
+Examples of custom queries in the wild:
+
+ * https://github.com/bostrt/squel-top-start-at (blog post about it: http://blog.bostrt.net/extending-squel-js/)
+
+
+## Non-standard SQL
+
+Squel supports the standard SQL commands and reserved words. However a number of database engines provide their own
+non-standard commands. To make things easy Squel allows for different 'flavours' of SQL to be loaded and used.
+
+At the moment Squel provides `mysql`, `mssql` and `postgres` flavours which augment query builders with additional commands (e.g. `INSERT ... RETURNING`
+for use with Postgres).
+
+To use this in node.js:
+
+```javascript
+var squel = require('squel').useFlavour('postgres');
+```
+
+For the browser:
+
+```html
+<script type="text/javascript" src="https://rawgithub.com/hiddentao/squel/master/squel.min.js"></script>
+<script type="text/javascript">
+  squel = squel.useFlavour('postgres');
+</script>
+```
+
+(Internally the flavour setup method simply utilizes the [custom query mechanism](http://hiddentao.github.io/squel/#custom_queries) to effect changes).
+
+Read the the [API docs](http://hiddentao.github.io/squel/api.html) to find out available commands. Flavours of SQL which get added to
+Squel in the future will be usable in the above manner.
+
+## Building it
+
+To build the code and run the tests:
+
+    $ npm install
+    $ npm test <-- this will build the code and run the tests
+
+## Releasing it
+
+Instructions for creating a new release of squel are in `RELEASE.md`.
+
+
+## Contributing
+
+Contributions are welcome! Please see `CONTRIBUTING.md`.
+
+
+## Ports to other languages
+
+* .NET - https://github.com/seymourpoler/PetProjects/tree/master/SQUEL
+
+## License
+
+MIT - see LICENSE.md

+ 13 - 0
www/lib/squel/RELEASE.md

@@ -0,0 +1,13 @@
+## Creating a new release
+
+1. Make final commits
+2. Push to `master` on Github and wait for build to pass
+3. Update `version` field in `package.json` and `bower.json`
+4. Update `CHANGELOG.md` with details of release
+5. Run `npm test`
+6. Commit and push to Github
+7. Tag commit as <version>
+8. Push all tags to Github: `git push --tags`
+9. Publish to NPM: `npm publish`
+10. Announce to world!
+

+ 3026 - 0
www/lib/squel/dist/squel-basic.js

@@ -0,0 +1,3026 @@
+;(function(root, factory) {
+  if (typeof define === 'function' && define.amd) {
+    define([], factory);
+  } else if (typeof exports === 'object') {
+    module.exports = factory();
+  } else {
+    root.squel = factory();
+  }
+}(this, function() {
+'use strict';
+
+var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } };
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; };
+
+function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
+
+function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+// append to string if non-empty
+function _pad(str, pad) {
+  return str.length ? str + pad : str;
+}
+
+// Extend given object's with other objects' properties, overriding existing ones if necessary
+function _extend(dst) {
+  for (var _len = arguments.length, sources = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
+    sources[_key - 1] = arguments[_key];
+  }
+
+  if (dst && sources) {
+    var _iteratorNormalCompletion = true;
+    var _didIteratorError = false;
+    var _iteratorError = undefined;
+
+    try {
+      var _loop = function _loop() {
+        var src = _step.value;
+
+        if ((typeof src === 'undefined' ? 'undefined' : _typeof(src)) === 'object') {
+          Object.getOwnPropertyNames(src).forEach(function (key) {
+            dst[key] = src[key];
+          });
+        }
+      };
+
+      for (var _iterator = sources[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
+        _loop();
+      }
+    } catch (err) {
+      _didIteratorError = true;
+      _iteratorError = err;
+    } finally {
+      try {
+        if (!_iteratorNormalCompletion && _iterator.return) {
+          _iterator.return();
+        }
+      } finally {
+        if (_didIteratorError) {
+          throw _iteratorError;
+        }
+      }
+    }
+  }
+
+  return dst;
+};
+
+// get whether object is a plain object
+function _isPlainObject(obj) {
+  return obj && obj.constructor.prototype === Object.prototype;
+};
+
+// get whether object is an array
+function _isArray(obj) {
+  return obj && obj.constructor.prototype === Array.prototype;
+};
+
+// get class name of given object
+function _getObjectClassName(obj) {
+  if (obj && obj.constructor && obj.constructor.toString) {
+    var arr = obj.constructor.toString().match(/function\s*(\w+)/);
+
+    if (arr && 2 === arr.length) {
+      return arr[1];
+    }
+  }
+}
+
+// clone given item
+function _clone(src) {
+  if (!src) {
+    return src;
+  }
+
+  if (typeof src.clone === 'function') {
+    return src.clone();
+  } else if (_isPlainObject(src) || _isArray(src)) {
+    var _ret2 = function () {
+      var ret = new src.constructor();
+
+      Object.getOwnPropertyNames(src).forEach(function (key) {
+        if (typeof src[key] !== 'function') {
+          ret[key] = _clone(src[key]);
+        }
+      });
+
+      return {
+        v: ret
+      };
+    }();
+
+    if ((typeof _ret2 === 'undefined' ? 'undefined' : _typeof(_ret2)) === "object") return _ret2.v;
+  } else {
+    return JSON.parse(JSON.stringify(src));
+  }
+};
+
+/**
+ * Register a value type handler
+ *
+ * Note: this will override any existing handler registered for this value type.
+ */
+function _registerValueHandler(handlers, type, handler) {
+  var typeofType = typeof type === 'undefined' ? 'undefined' : _typeof(type);
+
+  if (typeofType !== 'function' && typeofType !== 'string') {
+    throw new Error("type must be a class constructor or string");
+  }
+
+  if (typeof handler !== 'function') {
+    throw new Error("handler must be a function");
+  }
+
+  var _iteratorNormalCompletion2 = true;
+  var _didIteratorError2 = false;
+  var _iteratorError2 = undefined;
+
+  try {
+    for (var _iterator2 = handlers[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
+      var typeHandler = _step2.value;
+
+      if (typeHandler.type === type) {
+        typeHandler.handler = handler;
+
+        return;
+      }
+    }
+  } catch (err) {
+    _didIteratorError2 = true;
+    _iteratorError2 = err;
+  } finally {
+    try {
+      if (!_iteratorNormalCompletion2 && _iterator2.return) {
+        _iterator2.return();
+      }
+    } finally {
+      if (_didIteratorError2) {
+        throw _iteratorError2;
+      }
+    }
+  }
+
+  handlers.push({
+    type: type,
+    handler: handler
+  });
+};
+
+/**
+ * Get value type handler for given type
+ */
+function getValueHandler(value) {
+  for (var _len2 = arguments.length, handlerLists = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
+    handlerLists[_key2 - 1] = arguments[_key2];
+  }
+
+  var _iteratorNormalCompletion3 = true;
+  var _didIteratorError3 = false;
+  var _iteratorError3 = undefined;
+
+  try {
+    for (var _iterator3 = handlerLists[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
+      var handlers = _step3.value;
+      var _iteratorNormalCompletion4 = true;
+      var _didIteratorError4 = false;
+      var _iteratorError4 = undefined;
+
+      try {
+        for (var _iterator4 = handlers[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) {
+          var typeHandler = _step4.value;
+
+          // if type is a string then use `typeof` or else use `instanceof`
+          if ((typeof value === 'undefined' ? 'undefined' : _typeof(value)) === typeHandler.type || typeof typeHandler.type !== 'string' && value instanceof typeHandler.type) {
+            return typeHandler.handler;
+          }
+        }
+      } catch (err) {
+        _didIteratorError4 = true;
+        _iteratorError4 = err;
+      } finally {
+        try {
+          if (!_iteratorNormalCompletion4 && _iterator4.return) {
+            _iterator4.return();
+          }
+        } finally {
+          if (_didIteratorError4) {
+            throw _iteratorError4;
+          }
+        }
+      }
+    }
+  } catch (err) {
+    _didIteratorError3 = true;
+    _iteratorError3 = err;
+  } finally {
+    try {
+      if (!_iteratorNormalCompletion3 && _iterator3.return) {
+        _iterator3.return();
+      }
+    } finally {
+      if (_didIteratorError3) {
+        throw _iteratorError3;
+      }
+    }
+  }
+};
+
+/**
+ * Build base squel classes and methods
+ */
+function _buildSquel() {
+  var flavour = arguments.length <= 0 || arguments[0] === undefined ? null : arguments[0];
+
+  var cls = {
+    _getObjectClassName: _getObjectClassName
+  };
+
+  // default query builder options
+  cls.DefaultQueryBuilderOptions = {
+    // If true then table names will be rendered inside quotes. The quote character used is configurable via the nameQuoteCharacter option.
+    autoQuoteTableNames: false,
+    // If true then field names will rendered inside quotes. The quote character used is configurable via the nameQuoteCharacter option.
+    autoQuoteFieldNames: false,
+    // If true then alias names will rendered inside quotes. The quote character used is configurable via the `tableAliasQuoteCharacter` and `fieldAliasQuoteCharacter` options.
+    autoQuoteAliasNames: true,
+    // If true then table alias names will rendered after AS keyword.
+    useAsForTableAliasNames: false,
+    // The quote character used for when quoting table and field names
+    nameQuoteCharacter: '`',
+    // The quote character used for when quoting table alias names
+    tableAliasQuoteCharacter: '`',
+    // The quote character used for when quoting table alias names
+    fieldAliasQuoteCharacter: '"',
+    // Custom value handlers where key is the value type and the value is the handler function
+    valueHandlers: [],
+    // Character used to represent a parameter value
+    parameterCharacter: '?',
+    // Numbered parameters returned from toParam() as $1, $2, etc.
+    numberedParameters: false,
+    // Numbered parameters prefix character(s)
+    numberedParametersPrefix: '$',
+    // Numbered parameters start at this number.
+    numberedParametersStartAt: 1,
+    // If true then replaces all single quotes within strings. The replacement string used is configurable via the `singleQuoteReplacement` option.
+    replaceSingleQuotes: false,
+    // The string to replace single quotes with in query strings
+    singleQuoteReplacement: '\'\'',
+    // String used to join individual blocks in a query when it's stringified
+    separator: ' ',
+    // Function for formatting string values prior to insertion into query string
+    stringFormatter: null
+  };
+
+  // Global custom value handlers for all instances of builder
+  cls.globalValueHandlers = [];
+
+  /*
+  # ---------------------------------------------------------------------------------------------------------
+  # ---------------------------------------------------------------------------------------------------------
+  # Custom value types
+  # ---------------------------------------------------------------------------------------------------------
+  # ---------------------------------------------------------------------------------------------------------
+   */
+
+  // Register a new value handler
+  cls.registerValueHandler = function (type, handler) {
+    _registerValueHandler(cls.globalValueHandlers, type, handler);
+  };
+
+  /*
+  # ---------------------------------------------------------------------------------------------------------
+  # ---------------------------------------------------------------------------------------------------------
+  # Base classes
+  # ---------------------------------------------------------------------------------------------------------
+  # ---------------------------------------------------------------------------------------------------------
+  */
+
+  // Base class for cloneable builders
+  cls.Cloneable = function () {
+    function _class() {
+      _classCallCheck(this, _class);
+    }
+
+    _createClass(_class, [{
+      key: 'clone',
+
+      /**
+       * Clone this builder
+       */
+      value: function clone() {
+        var newInstance = new this.constructor();
+
+        return _extend(newInstance, _clone(_extend({}, this)));
+      }
+    }]);
+
+    return _class;
+  }();
+
+  // Base class for all builders
+  cls.BaseBuilder = function (_cls$Cloneable) {
+    _inherits(_class2, _cls$Cloneable);
+
+    /**
+     * Constructor.
+     * this.param  {Object} options Overriding one or more of `cls.DefaultQueryBuilderOptions`.
+     */
+
+    function _class2(options) {
+      _classCallCheck(this, _class2);
+
+      var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(_class2).call(this));
+
+      var defaults = JSON.parse(JSON.stringify(cls.DefaultQueryBuilderOptions));
+
+      _this.options = _extend({}, defaults, options);
+      return _this;
+    }
+
+    /**
+     * Register a custom value handler for this builder instance.
+     *
+     * Note: this will override any globally registered handler for this value type.
+     */
+
+
+    _createClass(_class2, [{
+      key: 'registerValueHandler',
+      value: function registerValueHandler(type, handler) {
+        _registerValueHandler(this.options.valueHandlers, type, handler);
+
+        return this;
+      }
+
+      /**
+       * Sanitize given expression.
+       */
+
+    }, {
+      key: '_sanitizeExpression',
+      value: function _sanitizeExpression(expr) {
+        // If it's not an Expression builder instance
+        if (!(expr instanceof cls.Expression)) {
+          // It must then be a string
+          if (typeof expr !== "string") {
+            throw new Error("expression must be a string or Expression instance");
+          }
+        }
+
+        return expr;
+      }
+
+      /**
+       * Sanitize the given name.
+       *
+       * The 'type' parameter is used to construct a meaningful error message in case validation fails.
+       */
+
+    }, {
+      key: '_sanitizeName',
+      value: function _sanitizeName(value, type) {
+        if (typeof value !== "string") {
+          throw new Error(type + ' must be a string');
+        }
+
+        return value;
+      }
+    }, {
+      key: '_sanitizeField',
+      value: function _sanitizeField(item) {
+        if (!(item instanceof cls.BaseBuilder)) {
+          item = this._sanitizeName(item, "field name");
+        }
+
+        return item;
+      }
+    }, {
+      key: '_sanitizeBaseBuilder',
+      value: function _sanitizeBaseBuilder(item) {
+        if (item instanceof cls.BaseBuilder) {
+          return item;
+        }
+
+        throw new Error("must be a BaseBuilder instance");
+      }
+    }, {
+      key: '_sanitizeTable',
+      value: function _sanitizeTable(item) {
+        if (typeof item !== "string") {
+          try {
+            item = this._sanitizeBaseBuilder(item);
+          } catch (e) {
+            throw new Error("table name must be a string or a query builder");
+          }
+        } else {
+          item = this._sanitizeName(item, 'table');
+        }
+
+        return item;
+      }
+    }, {
+      key: '_sanitizeTableAlias',
+      value: function _sanitizeTableAlias(item) {
+        return this._sanitizeName(item, "table alias");
+      }
+    }, {
+      key: '_sanitizeFieldAlias',
+      value: function _sanitizeFieldAlias(item) {
+        return this._sanitizeName(item, "field alias");
+      }
+
+      // Sanitize the given limit/offset value.
+
+    }, {
+      key: '_sanitizeLimitOffset',
+      value: function _sanitizeLimitOffset(value) {
+        value = parseInt(value);
+
+        if (0 > value || isNaN(value)) {
+          throw new Error("limit/offset must be >= 0");
+        }
+
+        return value;
+      }
+
+      // Santize the given field value
+
+    }, {
+      key: '_sanitizeValue',
+      value: function _sanitizeValue(item) {
+        var itemType = typeof item === 'undefined' ? 'undefined' : _typeof(item);
+
+        if (null === item) {
+          // null is allowed
+        } else if ("string" === itemType || "number" === itemType || "boolean" === itemType) {
+            // primitives are allowed
+          } else if (item instanceof cls.BaseBuilder) {
+              // Builders allowed
+            } else {
+                var typeIsValid = !!getValueHandler(item, this.options.valueHandlers, cls.globalValueHandlers);
+
+                if (!typeIsValid) {
+                  throw new Error("field value must be a string, number, boolean, null or one of the registered custom value types");
+                }
+              }
+
+        return item;
+      }
+
+      // Escape a string value, e.g. escape quotes and other characters within it.
+
+    }, {
+      key: '_escapeValue',
+      value: function _escapeValue(value) {
+        return !this.options.replaceSingleQuotes ? value : value.replace(/\'/g, this.options.singleQuoteReplacement);
+      }
+    }, {
+      key: '_formatTableName',
+      value: function _formatTableName(item) {
+        if (this.options.autoQuoteTableNames) {
+          var quoteChar = this.options.nameQuoteCharacter;
+
+          item = '' + quoteChar + item + quoteChar;
+        }
+
+        return item;
+      }
+    }, {
+      key: '_formatFieldAlias',
+      value: function _formatFieldAlias(item) {
+        if (this.options.autoQuoteAliasNames) {
+          var quoteChar = this.options.fieldAliasQuoteCharacter;
+
+          item = '' + quoteChar + item + quoteChar;
+        }
+
+        return item;
+      }
+    }, {
+      key: '_formatTableAlias',
+      value: function _formatTableAlias(item) {
+        if (this.options.autoQuoteAliasNames) {
+          var quoteChar = this.options.tableAliasQuoteCharacter;
+
+          item = '' + quoteChar + item + quoteChar;
+        }
+
+        return this.options.useAsForTableAliasNames ? 'AS ' + item : item;
+      }
+    }, {
+      key: '_formatFieldName',
+      value: function _formatFieldName(item) {
+        var _this2 = this;
+
+        var formattingOptions = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
+
+        if (this.options.autoQuoteFieldNames) {
+          (function () {
+            var quoteChar = _this2.options.nameQuoteCharacter;
+
+            if (formattingOptions.ignorePeriodsForFieldNameQuotes) {
+              // a.b.c -> `a.b.c`
+              item = '' + quoteChar + item + quoteChar;
+            } else {
+              // a.b.c -> `a`.`b`.`c`
+              item = item.split('.').map(function (v) {
+                // treat '*' as special case (#79)
+                return '*' === v ? v : '' + quoteChar + v + quoteChar;
+              }).join('.');
+            }
+          })();
+        }
+
+        return item;
+      }
+
+      // Format the given custom value
+
+    }, {
+      key: '_formatCustomValue',
+      value: function _formatCustomValue(value) {
+        var asParam = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1];
+
+        // user defined custom handlers takes precedence
+        var customHandler = getValueHandler(value, this.options.valueHandlers, cls.globalValueHandlers);
+
+        // use the custom handler if available
+        if (customHandler) {
+          value = customHandler(value, asParam);
+        }
+
+        return value;
+      }
+
+      /** 
+       * Format given value for inclusion into parameter values array.
+       */
+
+    }, {
+      key: '_formatValueForParamArray',
+      value: function _formatValueForParamArray(value) {
+        var _this3 = this;
+
+        if (_isArray(value)) {
+          return value.map(function (v) {
+            return _this3._formatValueForParamArray(v);
+          });
+        } else {
+          return this._formatCustomValue(value, true);
+        }
+      }
+
+      /**
+       * Format the given field value for inclusion into the query string
+       */
+
+    }, {
+      key: '_formatValueForQueryString',
+      value: function _formatValueForQueryString(value) {
+        var _this4 = this;
+
+        var formattingOptions = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
+
+        var customFormattedValue = this._formatCustomValue(value);
+
+        // if formatting took place then return it directly
+        if (customFormattedValue !== value) {
+          return this._applyNestingFormatting(customFormattedValue);
+        }
+
+        // if it's an array then format each element separately
+        if (_isArray(value)) {
+          value = value.map(function (v) {
+            return _this4._formatValueForQueryString(v);
+          });
+
+          value = this._applyNestingFormatting(value.join(', '));
+        } else {
+          var typeofValue = typeof value === 'undefined' ? 'undefined' : _typeof(value);
+
+          if (null === value) {
+            value = "NULL";
+          } else if (typeofValue === "boolean") {
+            value = value ? "TRUE" : "FALSE";
+          } else if (value instanceof cls.BaseBuilder) {
+            value = this._applyNestingFormatting(value.toString());
+          } else if (typeofValue !== "number") {
+            // if it's a string and we have custom string formatting turned on then use that
+            if ('string' === typeofValue && this.options.stringFormatter) {
+              return this.options.stringFormatter(value);
+            }
+
+            if (formattingOptions.dontQuote) {
+              value = '' + value;
+            } else {
+              var escapedValue = this._escapeValue(value);
+
+              value = '\'' + escapedValue + '\'';
+            }
+          }
+        }
+
+        return value;
+      }
+    }, {
+      key: '_applyNestingFormatting',
+      value: function _applyNestingFormatting(str) {
+        var nesting = arguments.length <= 1 || arguments[1] === undefined ? true : arguments[1];
+
+        if (str && typeof str === 'string' && nesting) {
+          // don't want to apply twice
+          if ('(' !== str.charAt(0) || ')' !== str.charAt(str.length - 1)) {
+            return '(' + str + ')';
+          }
+        }
+
+        return str;
+      }
+
+      /** 
+       * Build given string and its corresponding parameter values into 
+       * output.
+       * 
+       * @param {String} str
+       * @param {Array}  values
+       * @param {Object} [options] Additional options.
+       * @param {Boolean} [options.buildParameterized] Whether to build paramterized string. Default is false.
+       * @param {Boolean} [options.nested] Whether this expression is nested within another.
+       * @param {Boolean} [options.formattingOptions] Formatting options for values in query string.
+       * @return {Object}
+       */
+
+    }, {
+      key: '_buildString',
+      value: function _buildString(str, values) {
+        var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2];
+        var nested = options.nested;
+        var buildParameterized = options.buildParameterized;
+        var formattingOptions = options.formattingOptions;
+
+
+        values = values || [];
+        str = str || '';
+
+        var formattedStr = '',
+            curValue = -1,
+            formattedValues = [];
+
+        var paramChar = this.options.parameterCharacter;
+
+        var idx = 0;
+
+        while (str.length > idx) {
+          // param char?
+          if (str.substr(idx, paramChar.length) === paramChar) {
+            var value = values[++curValue];
+
+            if (buildParameterized) {
+              if (value instanceof cls.BaseBuilder) {
+                var ret = value._toParamString({
+                  buildParameterized: buildParameterized,
+                  nested: true
+                });
+
+                formattedStr += ret.text;
+                formattedValues.push.apply(formattedValues, _toConsumableArray(ret.values));
+              } else {
+                value = this._formatValueForParamArray(value);
+
+                if (_isArray(value)) {
+                  // Array(6) -> "(??, ??, ??, ??, ??, ??)"
+                  var tmpStr = value.map(function () {
+                    return paramChar;
+                  }).join(', ');
+
+                  formattedStr += '(' + tmpStr + ')';
+
+                  formattedValues.push.apply(formattedValues, _toConsumableArray(value));
+                } else {
+                  formattedStr += paramChar;
+
+                  formattedValues.push(value);
+                }
+              }
+            } else {
+              formattedStr += this._formatValueForQueryString(value, formattingOptions);
+            }
+
+            idx += paramChar.length;
+          } else {
+            formattedStr += str.charAt(idx);
+
+            idx++;
+          }
+        }
+
+        return {
+          text: this._applyNestingFormatting(formattedStr, !!nested),
+          values: formattedValues
+        };
+      }
+
+      /** 
+       * Build all given strings and their corresponding parameter values into 
+       * output.
+       * 
+       * @param {Array} strings
+       * @param {Array}  strValues array of value arrays corresponding to each string.
+       * @param {Object} [options] Additional options.
+       * @param {Boolean} [options.buildParameterized] Whether to build paramterized string. Default is false.
+       * @param {Boolean} [options.nested] Whether this expression is nested within another.
+       * @return {Object}
+       */
+
+    }, {
+      key: '_buildManyStrings',
+      value: function _buildManyStrings(strings, strValues) {
+        var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2];
+
+        var totalStr = [],
+            totalValues = [];
+
+        for (var idx = 0; strings.length > idx; ++idx) {
+          var inputString = strings[idx],
+              inputValues = strValues[idx];
+
+          var _buildString2 = this._buildString(inputString, inputValues, {
+            buildParameterized: options.buildParameterized,
+            nested: false
+          });
+
+          var text = _buildString2.text;
+          var values = _buildString2.values;
+
+
+          totalStr.push(text);
+          totalValues.push.apply(totalValues, _toConsumableArray(values));
+        }
+
+        totalStr = totalStr.join(this.options.separator);
+
+        return {
+          text: totalStr.length ? this._applyNestingFormatting(totalStr, !!options.nested) : '',
+          values: totalValues
+        };
+      }
+
+      /**
+       * Get parameterized representation of this instance.
+       * 
+       * @param {Object} [options] Options.
+       * @param {Boolean} [options.buildParameterized] Whether to build paramterized string. Default is false.
+       * @param {Boolean} [options.nested] Whether this expression is nested within another.
+       * @return {Object}
+       */
+
+    }, {
+      key: '_toParamString',
+      value: function _toParamString(options) {
+        throw new Error('Not yet implemented');
+      }
+
+      /**
+       * Get the expression string.
+       * @return {String}
+       */
+
+    }, {
+      key: 'toString',
+      value: function toString() {
+        var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+        return this._toParamString(options).text;
+      }
+
+      /**
+       * Get the parameterized expression string.
+       * @return {Object}
+       */
+
+    }, {
+      key: 'toParam',
+      value: function toParam() {
+        var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+        return this._toParamString(_extend({}, options, {
+          buildParameterized: true
+        }));
+      }
+    }]);
+
+    return _class2;
+  }(cls.Cloneable);
+
+  /*
+  # ---------------------------------------------------------------------------------------------------------
+  # ---------------------------------------------------------------------------------------------------------
+  # cls.Expressions
+  # ---------------------------------------------------------------------------------------------------------
+  # ---------------------------------------------------------------------------------------------------------
+  */
+
+  /**
+   * An SQL expression builder.
+   *
+   * SQL expressions are used in WHERE and ON clauses to filter data by various criteria.
+   *
+   * Expressions can be nested. Nested expression contains can themselves 
+   * contain nested expressions. When rendered a nested expression will be 
+   * fully contained within brackets.
+   * 
+   * All the build methods in this object return the object instance for chained method calling purposes.
+   */
+  cls.Expression = function (_cls$BaseBuilder) {
+    _inherits(_class3, _cls$BaseBuilder);
+
+    // Initialise the expression.
+
+    function _class3(options) {
+      _classCallCheck(this, _class3);
+
+      var _this5 = _possibleConstructorReturn(this, Object.getPrototypeOf(_class3).call(this, options));
+
+      _this5._nodes = [];
+      return _this5;
+    }
+
+    // Combine the current expression with the given expression using the intersection operator (AND).
+
+
+    _createClass(_class3, [{
+      key: 'and',
+      value: function and(expr) {
+        for (var _len3 = arguments.length, params = Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) {
+          params[_key3 - 1] = arguments[_key3];
+        }
+
+        expr = this._sanitizeExpression(expr);
+
+        this._nodes.push({
+          type: 'AND',
+          expr: expr,
+          para: params
+        });
+
+        return this;
+      }
+
+      // Combine the current expression with the given expression using the union operator (OR).
+
+    }, {
+      key: 'or',
+      value: function or(expr) {
+        for (var _len4 = arguments.length, params = Array(_len4 > 1 ? _len4 - 1 : 0), _key4 = 1; _key4 < _len4; _key4++) {
+          params[_key4 - 1] = arguments[_key4];
+        }
+
+        expr = this._sanitizeExpression(expr);
+
+        this._nodes.push({
+          type: 'OR',
+          expr: expr,
+          para: params
+        });
+
+        return this;
+      }
+    }, {
+      key: '_toParamString',
+      value: function _toParamString() {
+        var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+        var totalStr = [],
+            totalValues = [];
+
+        var _iteratorNormalCompletion5 = true;
+        var _didIteratorError5 = false;
+        var _iteratorError5 = undefined;
+
+        try {
+          for (var _iterator5 = this._nodes[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) {
+            var node = _step5.value;
+            var type = node.type;
+            var expr = node.expr;
+            var para = node.para;
+
+            var _ref = expr instanceof cls.Expression ? expr._toParamString({
+              buildParameterized: options.buildParameterized,
+              nested: true
+            }) : this._buildString(expr, para, {
+              buildParameterized: options.buildParameterized
+            });
+
+            var text = _ref.text;
+            var values = _ref.values;
+
+
+            if (totalStr.length) {
+              totalStr.push(type);
+            }
+
+            totalStr.push(text);
+            totalValues.push.apply(totalValues, _toConsumableArray(values));
+          }
+        } catch (err) {
+          _didIteratorError5 = true;
+          _iteratorError5 = err;
+        } finally {
+          try {
+            if (!_iteratorNormalCompletion5 && _iterator5.return) {
+              _iterator5.return();
+            }
+          } finally {
+            if (_didIteratorError5) {
+              throw _iteratorError5;
+            }
+          }
+        }
+
+        totalStr = totalStr.join(' ');
+
+        return {
+          text: this._applyNestingFormatting(totalStr, !!options.nested),
+          values: totalValues
+        };
+      }
+    }]);
+
+    return _class3;
+  }(cls.BaseBuilder);
+
+  /*
+  # ---------------------------------------------------------------------------------------------------------
+  # ---------------------------------------------------------------------------------------------------------
+  # cls.Case
+  # ---------------------------------------------------------------------------------------------------------
+  # ---------------------------------------------------------------------------------------------------------
+  */
+
+  /**
+   * An SQL CASE expression builder.
+   *
+   * SQL cases are used to select proper values based on specific criteria.
+   */
+  cls.Case = function (_cls$BaseBuilder2) {
+    _inherits(_class4, _cls$BaseBuilder2);
+
+    function _class4(fieldName) {
+      var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
+
+      _classCallCheck(this, _class4);
+
+      var _this6 = _possibleConstructorReturn(this, Object.getPrototypeOf(_class4).call(this, options));
+
+      if (_isPlainObject(fieldName)) {
+        options = fieldName;
+
+        fieldName = null;
+      }
+
+      if (fieldName) {
+        _this6._fieldName = _this6._sanitizeField(fieldName);
+      }
+
+      _this6.options = _extend({}, cls.DefaultQueryBuilderOptions, options);
+
+      _this6._cases = [];
+      _this6._elseValue = null;
+      return _this6;
+    }
+
+    _createClass(_class4, [{
+      key: 'when',
+      value: function when(expression) {
+        for (var _len5 = arguments.length, values = Array(_len5 > 1 ? _len5 - 1 : 0), _key5 = 1; _key5 < _len5; _key5++) {
+          values[_key5 - 1] = arguments[_key5];
+        }
+
+        this._cases.unshift({
+          expression: expression,
+          values: values
+        });
+
+        return this;
+      }
+    }, {
+      key: 'then',
+      value: function then(result) {
+        if (this._cases.length == 0) {
+          throw new Error("when() needs to be called first");
+        }
+
+        this._cases[0].result = result;
+
+        return this;
+      }
+    }, {
+      key: 'else',
+      value: function _else(elseValue) {
+        this._elseValue = elseValue;
+
+        return this;
+      }
+    }, {
+      key: '_toParamString',
+      value: function _toParamString() {
+        var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+        var totalStr = '',
+            totalValues = [];
+
+        var _iteratorNormalCompletion6 = true;
+        var _didIteratorError6 = false;
+        var _iteratorError6 = undefined;
+
+        try {
+          for (var _iterator6 = this._cases[Symbol.iterator](), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) {
+            var _step6$value = _step6.value;
+            var expression = _step6$value.expression;
+            var values = _step6$value.values;
+            var result = _step6$value.result;
+
+            totalStr = _pad(totalStr, ' ');
+
+            var ret = this._buildString(expression, values, {
+              buildParameterized: options.buildParameterized,
+              nested: true
+            });
+
+            totalStr += 'WHEN ' + ret.text + ' THEN ' + this._formatValueForQueryString(result);
+            totalValues.push.apply(totalValues, _toConsumableArray(ret.values));
+          }
+        } catch (err) {
+          _didIteratorError6 = true;
+          _iteratorError6 = err;
+        } finally {
+          try {
+            if (!_iteratorNormalCompletion6 && _iterator6.return) {
+              _iterator6.return();
+            }
+          } finally {
+            if (_didIteratorError6) {
+              throw _iteratorError6;
+            }
+          }
+        }
+
+        if (totalStr.length) {
+          totalStr += ' ELSE ' + this._formatValueForQueryString(this._elseValue) + ' END';
+
+          if (this._fieldName) {
+            totalStr = this._fieldName + ' ' + totalStr;
+          }
+
+          totalStr = 'CASE ' + totalStr;
+        } else {
+          totalStr = this._formatValueForQueryString(this._elseValue);
+        }
+
+        return {
+          text: totalStr,
+          values: totalValues
+        };
+      }
+    }]);
+
+    return _class4;
+  }(cls.BaseBuilder);
+
+  /*
+  # ---------------------------------------------------------------------------------------------------------
+  # ---------------------------------------------------------------------------------------------------------
+  # Building blocks
+  # ---------------------------------------------------------------------------------------------------------
+  # ---------------------------------------------------------------------------------------------------------
+  */
+
+  /*
+  # A building block represents a single build-step within a query building process.
+  #
+  # Query builders consist of one or more building blocks which get run in a particular order. Building blocks can
+  # optionally specify methods to expose through the query builder interface. They can access all the input data for
+  # the query builder and manipulate it as necessary, as well as append to the final query string output.
+  #
+  # If you wish to customize how queries get built or add proprietary query phrases and content then it is recommended
+  # that you do so using one or more custom building blocks.
+  #
+  # Original idea posted in https://github.com/hiddentao/export/issues/10#issuecomment-15016427
+  */
+  cls.Block = function (_cls$BaseBuilder3) {
+    _inherits(_class5, _cls$BaseBuilder3);
+
+    function _class5(options) {
+      _classCallCheck(this, _class5);
+
+      return _possibleConstructorReturn(this, Object.getPrototypeOf(_class5).call(this, options));
+    }
+
+    /**
+    # Get input methods to expose within the query builder.
+    #
+    # By default all methods except the following get returned:
+    #   methods prefixed with _
+    #   constructor and toString()
+    #
+    # @return Object key -> function pairs
+    */
+
+
+    _createClass(_class5, [{
+      key: 'exposedMethods',
+      value: function exposedMethods() {
+        var ret = {};
+
+        var obj = this;
+
+        while (obj) {
+          Object.getOwnPropertyNames(obj).forEach(function (prop) {
+            if ('constructor' !== prop && typeof obj[prop] === "function" && prop.charAt(0) !== '_' && !cls.Block.prototype[prop]) {
+              ret[prop] = obj[prop];
+            }
+          });
+
+          obj = Object.getPrototypeOf(obj);
+        };
+
+        return ret;
+      }
+    }]);
+
+    return _class5;
+  }(cls.BaseBuilder);
+
+  // A fixed string which always gets output
+  cls.StringBlock = function (_cls$Block) {
+    _inherits(_class6, _cls$Block);
+
+    function _class6(options, str) {
+      _classCallCheck(this, _class6);
+
+      var _this8 = _possibleConstructorReturn(this, Object.getPrototypeOf(_class6).call(this, options));
+
+      _this8._str = str;
+      return _this8;
+    }
+
+    _createClass(_class6, [{
+      key: '_toParamString',
+      value: function _toParamString() {
+        var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+        return {
+          text: this._str,
+          values: []
+        };
+      }
+    }]);
+
+    return _class6;
+  }(cls.Block);
+
+  // A function string block
+  cls.FunctionBlock = function (_cls$Block2) {
+    _inherits(_class7, _cls$Block2);
+
+    function _class7(options) {
+      _classCallCheck(this, _class7);
+
+      var _this9 = _possibleConstructorReturn(this, Object.getPrototypeOf(_class7).call(this, options));
+
+      _this9._strings = [];
+      _this9._values = [];
+      return _this9;
+    }
+
+    _createClass(_class7, [{
+      key: 'function',
+      value: function _function(str) {
+        this._strings.push(str);
+
+        for (var _len6 = arguments.length, values = Array(_len6 > 1 ? _len6 - 1 : 0), _key6 = 1; _key6 < _len6; _key6++) {
+          values[_key6 - 1] = arguments[_key6];
+        }
+
+        this._values.push(values);
+      }
+    }, {
+      key: '_toParamString',
+      value: function _toParamString() {
+        var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+        return this._buildManyStrings(this._strings, this._values, options);
+      }
+    }]);
+
+    return _class7;
+  }(cls.Block);
+
+  // value handler for FunctionValueBlock objects
+  cls.registerValueHandler(cls.FunctionBlock, function (value) {
+    var asParam = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1];
+
+    return asParam ? value.toParam() : value.toString();
+  });
+
+  /*
+  # Table specifier base class
+  */
+  cls.AbstractTableBlock = function (_cls$Block3) {
+    _inherits(_class8, _cls$Block3);
+
+    /**
+     * @param {Boolean} [options.singleTable] If true then only allow one table spec.
+     * @param {String} [options.prefix] String prefix for output.
+     */
+
+    function _class8(options, prefix) {
+      _classCallCheck(this, _class8);
+
+      var _this10 = _possibleConstructorReturn(this, Object.getPrototypeOf(_class8).call(this, options));
+
+      _this10._tables = [];
+      return _this10;
+    }
+
+    /**
+    # Update given table.
+    #
+    # An alias may also be specified for the table.
+    #
+    # Concrete subclasses should provide a method which calls this
+    */
+
+
+    _createClass(_class8, [{
+      key: '_table',
+      value: function _table(table) {
+        var alias = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+
+        alias = alias ? this._sanitizeTableAlias(alias) : alias;
+        table = this._sanitizeTable(table);
+
+        if (this.options.singleTable) {
+          this._tables = [];
+        }
+
+        this._tables.push({
+          table: table,
+          alias: alias
+        });
+      }
+
+      // get whether a table has been set
+
+    }, {
+      key: '_hasTable',
+      value: function _hasTable() {
+        return 0 < this._tables.length;
+      }
+
+      /**
+       * @override
+       */
+
+    }, {
+      key: '_toParamString',
+      value: function _toParamString() {
+        var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+        var totalStr = '',
+            totalValues = [];
+
+        if (this._hasTable()) {
+          // retrieve the parameterised queries
+          var _iteratorNormalCompletion7 = true;
+          var _didIteratorError7 = false;
+          var _iteratorError7 = undefined;
+
+          try {
+            for (var _iterator7 = this._tables[Symbol.iterator](), _step7; !(_iteratorNormalCompletion7 = (_step7 = _iterator7.next()).done); _iteratorNormalCompletion7 = true) {
+              var _step7$value = _step7.value;
+              var table = _step7$value.table;
+              var alias = _step7$value.alias;
+
+              totalStr = _pad(totalStr, ', ');
+
+              var tableStr = void 0;
+
+              if (table instanceof cls.BaseBuilder) {
+                var _table$_toParamString = table._toParamString({
+                  buildParameterized: options.buildParameterized,
+                  nested: true
+                });
+
+                var text = _table$_toParamString.text;
+                var values = _table$_toParamString.values;
+
+
+                tableStr = text;
+                totalValues.push.apply(totalValues, _toConsumableArray(values));
+              } else {
+                tableStr = this._formatTableName(table);
+              }
+
+              if (alias) {
+                tableStr += ' ' + this._formatTableAlias(alias);
+              }
+
+              totalStr += tableStr;
+            }
+          } catch (err) {
+            _didIteratorError7 = true;
+            _iteratorError7 = err;
+          } finally {
+            try {
+              if (!_iteratorNormalCompletion7 && _iterator7.return) {
+                _iterator7.return();
+              }
+            } finally {
+              if (_didIteratorError7) {
+                throw _iteratorError7;
+              }
+            }
+          }
+
+          if (this.options.prefix) {
+            totalStr = this.options.prefix + ' ' + totalStr;
+          }
+        }
+
+        return {
+          text: totalStr,
+          values: totalValues
+        };
+      }
+    }]);
+
+    return _class8;
+  }(cls.Block);
+
+  // target table for DELETE queries, DELETE <??> FROM
+  cls.TargetTableBlock = function (_cls$AbstractTableBlo) {
+    _inherits(_class9, _cls$AbstractTableBlo);
+
+    function _class9() {
+      _classCallCheck(this, _class9);
+
+      return _possibleConstructorReturn(this, Object.getPrototypeOf(_class9).apply(this, arguments));
+    }
+
+    _createClass(_class9, [{
+      key: 'target',
+      value: function target(table) {
+        this._table(table);
+      }
+    }]);
+
+    return _class9;
+  }(cls.AbstractTableBlock);
+
+  // Update Table
+  cls.UpdateTableBlock = function (_cls$AbstractTableBlo2) {
+    _inherits(_class10, _cls$AbstractTableBlo2);
+
+    function _class10() {
+      _classCallCheck(this, _class10);
+
+      return _possibleConstructorReturn(this, Object.getPrototypeOf(_class10).apply(this, arguments));
+    }
+
+    _createClass(_class10, [{
+      key: 'table',
+      value: function table(_table2) {
+        var alias = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+
+        this._table(_table2, alias);
+      }
+    }, {
+      key: '_toParamString',
+      value: function _toParamString() {
+        var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+        if (!this._hasTable()) {
+          throw new Error("table() needs to be called");
+        }
+
+        return _get(Object.getPrototypeOf(_class10.prototype), '_toParamString', this).call(this, options);
+      }
+    }]);
+
+    return _class10;
+  }(cls.AbstractTableBlock);
+
+  // FROM table
+  cls.FromTableBlock = function (_cls$AbstractTableBlo3) {
+    _inherits(_class11, _cls$AbstractTableBlo3);
+
+    function _class11(options) {
+      _classCallCheck(this, _class11);
+
+      return _possibleConstructorReturn(this, Object.getPrototypeOf(_class11).call(this, _extend({}, options, {
+        prefix: 'FROM'
+      })));
+    }
+
+    _createClass(_class11, [{
+      key: 'from',
+      value: function from(table) {
+        var alias = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+
+        this._table(table, alias);
+      }
+    }]);
+
+    return _class11;
+  }(cls.AbstractTableBlock);
+
+  // INTO table
+  cls.IntoTableBlock = function (_cls$AbstractTableBlo4) {
+    _inherits(_class12, _cls$AbstractTableBlo4);
+
+    function _class12(options) {
+      _classCallCheck(this, _class12);
+
+      return _possibleConstructorReturn(this, Object.getPrototypeOf(_class12).call(this, _extend({}, options, {
+        prefix: 'INTO',
+        singleTable: true
+      })));
+    }
+
+    _createClass(_class12, [{
+      key: 'into',
+      value: function into(table) {
+        this._table(table);
+      }
+    }, {
+      key: '_toParamString',
+      value: function _toParamString() {
+        var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+        if (!this._hasTable()) {
+          throw new Error("into() needs to be called");
+        }
+
+        return _get(Object.getPrototypeOf(_class12.prototype), '_toParamString', this).call(this, options);
+      }
+    }]);
+
+    return _class12;
+  }(cls.AbstractTableBlock);
+
+  // (SELECT) Get field
+  cls.GetFieldBlock = function (_cls$Block4) {
+    _inherits(_class13, _cls$Block4);
+
+    function _class13(options) {
+      _classCallCheck(this, _class13);
+
+      var _this15 = _possibleConstructorReturn(this, Object.getPrototypeOf(_class13).call(this, options));
+
+      _this15._fields = [];
+      return _this15;
+    }
+
+    /**
+    # Add the given fields to the final result set.
+    #
+    # The parameter is an Object containing field names (or database functions) as the keys and aliases for the fields
+    # as the values. If the value for a key is null then no alias is set for that field.
+    #
+    # Internally this method simply calls the field() method of this block to add each individual field.
+    #
+    # options.ignorePeriodsForFieldNameQuotes - whether to ignore period (.) when automatically quoting the field name
+    */
+
+
+    _createClass(_class13, [{
+      key: 'fields',
+      value: function fields(_fields) {
+        var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
+
+        if (_isArray(_fields)) {
+          var _iteratorNormalCompletion8 = true;
+          var _didIteratorError8 = false;
+          var _iteratorError8 = undefined;
+
+          try {
+            for (var _iterator8 = _fields[Symbol.iterator](), _step8; !(_iteratorNormalCompletion8 = (_step8 = _iterator8.next()).done); _iteratorNormalCompletion8 = true) {
+              var field = _step8.value;
+
+              this.field(field, null, options);
+            }
+          } catch (err) {
+            _didIteratorError8 = true;
+            _iteratorError8 = err;
+          } finally {
+            try {
+              if (!_iteratorNormalCompletion8 && _iterator8.return) {
+                _iterator8.return();
+              }
+            } finally {
+              if (_didIteratorError8) {
+                throw _iteratorError8;
+              }
+            }
+          }
+        } else {
+          for (var _field2 in _fields) {
+            var alias = _fields[_field2];
+
+            this.field(_field2, alias, options);
+          }
+        }
+      }
+
+      /**
+      # Add the given field to the final result set.
+      #
+      # The 'field' parameter does not necessarily have to be a fieldname. It can use database functions too,
+      # e.g. DATE_FORMAT(a.started, "%H")
+      #
+      # An alias may also be specified for this field.
+      #
+      # options.ignorePeriodsForFieldNameQuotes - whether to ignore period (.) when automatically quoting the field name
+      */
+
+    }, {
+      key: 'field',
+      value: function field(_field) {
+        var alias = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+        var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2];
+
+        alias = alias ? this._sanitizeFieldAlias(alias) : alias;
+        _field = this._sanitizeField(_field);
+
+        // if field-alias combo already present then don't add
+        var existingField = this._fields.filter(function (f) {
+          return f.name === _field && f.alias === alias;
+        });
+        if (existingField.length) {
+          return this;
+        }
+
+        this._fields.push({
+          name: _field,
+          alias: alias,
+          options: options
+        });
+      }
+    }, {
+      key: '_toParamString',
+      value: function _toParamString() {
+        var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+        var queryBuilder = options.queryBuilder;
+        var buildParameterized = options.buildParameterized;
+
+
+        var totalStr = '',
+            totalValues = [];
+
+        var _iteratorNormalCompletion9 = true;
+        var _didIteratorError9 = false;
+        var _iteratorError9 = undefined;
+
+        try {
+          for (var _iterator9 = this._fields[Symbol.iterator](), _step9; !(_iteratorNormalCompletion9 = (_step9 = _iterator9.next()).done); _iteratorNormalCompletion9 = true) {
+            var field = _step9.value;
+
+            totalStr = _pad(totalStr, ", ");
+
+            var name = field.name;
+            var alias = field.alias;
+            var _options = field.options;
+
+
+            if (typeof name === 'string') {
+              totalStr += this._formatFieldName(name, _options);
+            } else {
+              var ret = name._toParamString({
+                nested: true,
+                buildParameterized: buildParameterized
+              });
+
+              totalStr += ret.text;
+              totalValues.push.apply(totalValues, _toConsumableArray(ret.values));
+            }
+
+            if (alias) {
+              totalStr += ' AS ' + this._formatFieldAlias(alias);
+            }
+          }
+        } catch (err) {
+          _didIteratorError9 = true;
+          _iteratorError9 = err;
+        } finally {
+          try {
+            if (!_iteratorNormalCompletion9 && _iterator9.return) {
+              _iterator9.return();
+            }
+          } finally {
+            if (_didIteratorError9) {
+              throw _iteratorError9;
+            }
+          }
+        }
+
+        if (!totalStr.length) {
+          // if select query and a table is set then all fields wanted
+          var fromTableBlock = queryBuilder && queryBuilder.getBlock(cls.FromTableBlock);
+          if (fromTableBlock && fromTableBlock._hasTable()) {
+            totalStr = "*";
+          }
+        }
+
+        return {
+          text: totalStr,
+          values: totalValues
+        };
+      }
+    }]);
+
+    return _class13;
+  }(cls.Block);
+
+  // Base class for setting fields to values (used for INSERT and UPDATE queries)
+  cls.AbstractSetFieldBlock = function (_cls$Block5) {
+    _inherits(_class14, _cls$Block5);
+
+    function _class14(options) {
+      _classCallCheck(this, _class14);
+
+      var _this16 = _possibleConstructorReturn(this, Object.getPrototypeOf(_class14).call(this, options));
+
+      _this16._reset();
+      return _this16;
+    }
+
+    _createClass(_class14, [{
+      key: '_reset',
+      value: function _reset() {
+        this._fields = [];
+        this._values = [[]];
+        this._valueOptions = [[]];
+      }
+
+      // Update the given field with the given value.
+      // This will override any previously set value for the given field.
+
+    }, {
+      key: '_set',
+      value: function _set(field, value) {
+        var valueOptions = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2];
+
+        if (this._values.length > 1) {
+          throw new Error("Cannot set multiple rows of fields this way.");
+        }
+
+        if (typeof value !== 'undefined') {
+          value = this._sanitizeValue(value);
+        }
+
+        field = this._sanitizeField(field);
+
+        // Explicity overwrite existing fields
+        var index = this._fields.indexOf(field);
+
+        // if field not defined before
+        if (-1 === index) {
+          this._fields.push(field);
+          index = this._fields.length - 1;
+        }
+
+        this._values[0][index] = value;
+        this._valueOptions[0][index] = valueOptions;
+      }
+
+      // Insert fields based on the key/value pairs in the given object
+
+    }, {
+      key: '_setFields',
+      value: function _setFields(fields) {
+        var valueOptions = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
+
+        if ((typeof fields === 'undefined' ? 'undefined' : _typeof(fields)) !== 'object') {
+          throw new Error("Expected an object but got " + (typeof fields === 'undefined' ? 'undefined' : _typeof(fields)));
+        }
+
+        for (var field in fields) {
+          this._set(field, fields[field], valueOptions);
+        }
+      }
+
+      // Insert multiple rows for the given fields. Accepts an array of objects.
+      // This will override all previously set values for every field.
+
+    }, {
+      key: '_setFieldsRows',
+      value: function _setFieldsRows(fieldsRows) {
+        var valueOptions = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
+
+        if (!_isArray(fieldsRows)) {
+          throw new Error("Expected an array of objects but got " + (typeof fieldsRows === 'undefined' ? 'undefined' : _typeof(fieldsRows)));
+        }
+
+        // Reset the objects stored fields and values
+        this._reset();
+
+        // for each row
+        for (var i = 0; fieldsRows.length > i; ++i) {
+          var fieldRow = fieldsRows[i];
+
+          // for each field
+          for (var field in fieldRow) {
+            var value = fieldRow[field];
+
+            field = this._sanitizeField(field);
+            value = this._sanitizeValue(value);
+
+            var index = this._fields.indexOf(field);
+
+            if (0 < i && -1 === index) {
+              throw new Error('All fields in subsequent rows must match the fields in the first row');
+            }
+
+            // Add field only if it hasn't been added before
+            if (-1 === index) {
+              this._fields.push(field);
+              index = this._fields.length - 1;
+            }
+
+            // The first value added needs to add the array
+            if (!_isArray(this._values[i])) {
+              this._values[i] = [];
+              this._valueOptions[i] = [];
+            }
+
+            this._values[i][index] = value;
+            this._valueOptions[i][index] = valueOptions;
+          }
+        }
+      }
+    }]);
+
+    return _class14;
+  }(cls.Block);
+
+  // (UPDATE) SET field=value
+  cls.SetFieldBlock = function (_cls$AbstractSetField) {
+    _inherits(_class15, _cls$AbstractSetField);
+
+    function _class15() {
+      _classCallCheck(this, _class15);
+
+      return _possibleConstructorReturn(this, Object.getPrototypeOf(_class15).apply(this, arguments));
+    }
+
+    _createClass(_class15, [{
+      key: 'set',
+      value: function set(field, value, options) {
+        this._set(field, value, options);
+      }
+    }, {
+      key: 'setFields',
+      value: function setFields(fields, valueOptions) {
+        this._setFields(fields, valueOptions);
+      }
+    }, {
+      key: '_toParamString',
+      value: function _toParamString() {
+        var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+        var buildParameterized = options.buildParameterized;
+
+
+        if (0 >= this._fields.length) {
+          throw new Error("set() needs to be called");
+        }
+
+        var totalStr = '',
+            totalValues = [];
+
+        for (var i = 0; i < this._fields.length; ++i) {
+          totalStr = _pad(totalStr, ', ');
+
+          var field = this._formatFieldName(this._fields[i]);
+          var value = this._values[0][i];
+
+          // e.g. field can be an expression such as `count = count + 1`
+          if (0 > field.indexOf('=')) {
+            field = field + ' = ' + this.options.parameterCharacter;
+          }
+
+          var ret = this._buildString(field, [value], {
+            buildParameterized: buildParameterized,
+            formattingOptions: this._valueOptions[0][i]
+          });
+
+          totalStr += ret.text;
+          totalValues.push.apply(totalValues, _toConsumableArray(ret.values));
+        }
+
+        return {
+          text: 'SET ' + totalStr,
+          values: totalValues
+        };
+      }
+    }]);
+
+    return _class15;
+  }(cls.AbstractSetFieldBlock);
+
+  // (INSERT INTO) ... field ... value
+  cls.InsertFieldValueBlock = function (_cls$AbstractSetField2) {
+    _inherits(_class16, _cls$AbstractSetField2);
+
+    function _class16() {
+      _classCallCheck(this, _class16);
+
+      return _possibleConstructorReturn(this, Object.getPrototypeOf(_class16).apply(this, arguments));
+    }
+
+    _createClass(_class16, [{
+      key: 'set',
+      value: function set(field, value) {
+        var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2];
+
+        this._set(field, value, options);
+      }
+    }, {
+      key: 'setFields',
+      value: function setFields(fields, valueOptions) {
+        this._setFields(fields, valueOptions);
+      }
+    }, {
+      key: 'setFieldsRows',
+      value: function setFieldsRows(fieldsRows, valueOptions) {
+        this._setFieldsRows(fieldsRows, valueOptions);
+      }
+    }, {
+      key: '_toParamString',
+      value: function _toParamString() {
+        var _this19 = this;
+
+        var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+        var buildParameterized = options.buildParameterized;
+
+
+        var fieldString = this._fields.map(function (f) {
+          return _this19._formatFieldName(f);
+        }).join(', ');
+
+        var valueStrings = [],
+            totalValues = [];
+
+        for (var i = 0; i < this._values.length; ++i) {
+          valueStrings[i] = '';
+
+          for (var j = 0; j < this._values[i].length; ++j) {
+            var ret = this._buildString(this.options.parameterCharacter, [this._values[i][j]], {
+              buildParameterized: buildParameterized,
+              formattingOptions: this._valueOptions[i][j]
+            });
+
+            totalValues.push.apply(totalValues, _toConsumableArray(ret.values));
+
+            valueStrings[i] = _pad(valueStrings[i], ', ');
+            valueStrings[i] += ret.text;
+          }
+        }
+
+        return {
+          text: fieldString.length ? '(' + fieldString + ') VALUES (' + valueStrings.join('), (') + ')' : '',
+          values: totalValues
+        };
+      }
+    }]);
+
+    return _class16;
+  }(cls.AbstractSetFieldBlock);
+
+  // (INSERT INTO) ... field ... (SELECT ... FROM ...)
+  cls.InsertFieldsFromQueryBlock = function (_cls$Block6) {
+    _inherits(_class17, _cls$Block6);
+
+    function _class17(options) {
+      _classCallCheck(this, _class17);
+
+      var _this20 = _possibleConstructorReturn(this, Object.getPrototypeOf(_class17).call(this, options));
+
+      _this20._fields = [];
+      _this20._query = null;
+      return _this20;
+    }
+
+    _createClass(_class17, [{
+      key: 'fromQuery',
+      value: function fromQuery(fields, selectQuery) {
+        var _this21 = this;
+
+        this._fields = fields.map(function (v) {
+          return _this21._sanitizeField(v);
+        });
+
+        this._query = this._sanitizeBaseBuilder(selectQuery);
+      }
+    }, {
+      key: '_toParamString',
+      value: function _toParamString() {
+        var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+        var totalStr = '',
+            totalValues = [];
+
+        if (this._fields.length && this._query) {
+          var _query$_toParamString = this._query._toParamString({
+            buildParameterized: options.buildParameterized,
+            nested: true
+          });
+
+          var text = _query$_toParamString.text;
+          var values = _query$_toParamString.values;
+
+
+          totalStr = '(' + this._fields.join(', ') + ') ' + this._applyNestingFormatting(text);
+          totalValues = values;
+        }
+
+        return {
+          text: totalStr,
+          values: totalValues
+        };
+      }
+    }]);
+
+    return _class17;
+  }(cls.Block);
+
+  // DISTINCT
+  cls.DistinctBlock = function (_cls$Block7) {
+    _inherits(_class18, _cls$Block7);
+
+    function _class18() {
+      _classCallCheck(this, _class18);
+
+      return _possibleConstructorReturn(this, Object.getPrototypeOf(_class18).apply(this, arguments));
+    }
+
+    _createClass(_class18, [{
+      key: 'distinct',
+
+      // Add the DISTINCT keyword to the query.
+      value: function distinct() {
+        this._useDistinct = true;
+      }
+    }, {
+      key: '_toParamString',
+      value: function _toParamString() {
+        return {
+          text: this._useDistinct ? "DISTINCT" : "",
+          values: []
+        };
+      }
+    }]);
+
+    return _class18;
+  }(cls.Block);
+
+  // GROUP BY
+  cls.GroupByBlock = function (_cls$Block8) {
+    _inherits(_class19, _cls$Block8);
+
+    function _class19(options) {
+      _classCallCheck(this, _class19);
+
+      var _this23 = _possibleConstructorReturn(this, Object.getPrototypeOf(_class19).call(this, options));
+
+      _this23._groups = [];
+      return _this23;
+    }
+
+    // Add a GROUP BY transformation for the given field.
+
+
+    _createClass(_class19, [{
+      key: 'group',
+      value: function group(field) {
+        this._groups.push(this._sanitizeField(field));
+      }
+    }, {
+      key: '_toParamString',
+      value: function _toParamString() {
+        var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+        return {
+          text: this._groups.length ? 'GROUP BY ' + this._groups.join(', ') : '',
+          values: []
+        };
+      }
+    }]);
+
+    return _class19;
+  }(cls.Block);
+
+  // OFFSET x
+  cls.OffsetBlock = function (_cls$Block9) {
+    _inherits(_class20, _cls$Block9);
+
+    function _class20(options) {
+      _classCallCheck(this, _class20);
+
+      var _this24 = _possibleConstructorReturn(this, Object.getPrototypeOf(_class20).call(this, options));
+
+      _this24._offsets = null;
+      return _this24;
+    }
+
+    /**
+    # Set the OFFSET transformation.
+    #
+    # Call this will override the previously set offset for this query. Also note that Passing 0 for 'max' will remove
+    # the offset.
+    */
+
+
+    _createClass(_class20, [{
+      key: 'offset',
+      value: function offset(start) {
+        this._offsets = this._sanitizeLimitOffset(start);
+      }
+    }, {
+      key: '_toParamString',
+      value: function _toParamString() {
+        return {
+          text: this._offsets ? 'OFFSET ' + this._offsets : '',
+          values: []
+        };
+      }
+    }]);
+
+    return _class20;
+  }(cls.Block);
+
+  // Abstract condition base class
+  cls.AbstractConditionBlock = function (_cls$Block10) {
+    _inherits(_class21, _cls$Block10);
+
+    /** 
+     * @param {String} options.verb The condition verb.
+     */
+
+    function _class21(options) {
+      _classCallCheck(this, _class21);
+
+      var _this25 = _possibleConstructorReturn(this, Object.getPrototypeOf(_class21).call(this, options));
+
+      _this25._conditions = [];
+      return _this25;
+    }
+
+    /**
+    # Add a condition.
+    #
+    # When the final query is constructed all the conditions are combined using the intersection (AND) operator.
+    #
+    # Concrete subclasses should provide a method which calls this
+    */
+
+
+    _createClass(_class21, [{
+      key: '_condition',
+      value: function _condition(condition) {
+        for (var _len7 = arguments.length, values = Array(_len7 > 1 ? _len7 - 1 : 0), _key7 = 1; _key7 < _len7; _key7++) {
+          values[_key7 - 1] = arguments[_key7];
+        }
+
+        condition = this._sanitizeExpression(condition);
+
+        this._conditions.push({
+          expr: condition,
+          values: values
+        });
+      }
+    }, {
+      key: '_toParamString',
+      value: function _toParamString() {
+        var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+        var totalStr = [],
+            totalValues = [];
+
+        var _iteratorNormalCompletion10 = true;
+        var _didIteratorError10 = false;
+        var _iteratorError10 = undefined;
+
+        try {
+          for (var _iterator10 = this._conditions[Symbol.iterator](), _step10; !(_iteratorNormalCompletion10 = (_step10 = _iterator10.next()).done); _iteratorNormalCompletion10 = true) {
+            var _step10$value = _step10.value;
+            var expr = _step10$value.expr;
+            var values = _step10$value.values;
+
+            var ret = expr instanceof cls.Expression ? expr._toParamString({
+              buildParameterized: options.buildParameterized
+            }) : this._buildString(expr, values, {
+              buildParameterized: options.buildParameterized
+            });
+
+            if (ret.text.length) {
+              totalStr.push(ret.text);
+            }
+
+            totalValues.push.apply(totalValues, _toConsumableArray(ret.values));
+          }
+        } catch (err) {
+          _didIteratorError10 = true;
+          _iteratorError10 = err;
+        } finally {
+          try {
+            if (!_iteratorNormalCompletion10 && _iterator10.return) {
+              _iterator10.return();
+            }
+          } finally {
+            if (_didIteratorError10) {
+              throw _iteratorError10;
+            }
+          }
+        }
+
+        if (totalStr.length) {
+          totalStr = totalStr.join(') AND (');
+        }
+
+        return {
+          text: totalStr.length ? this.options.verb + ' (' + totalStr + ')' : '',
+          values: totalValues
+        };
+      }
+    }]);
+
+    return _class21;
+  }(cls.Block);
+
+  // WHERE
+  cls.WhereBlock = function (_cls$AbstractConditio) {
+    _inherits(_class22, _cls$AbstractConditio);
+
+    function _class22(options) {
+      _classCallCheck(this, _class22);
+
+      return _possibleConstructorReturn(this, Object.getPrototypeOf(_class22).call(this, _extend({}, options, {
+        verb: 'WHERE'
+      })));
+    }
+
+    _createClass(_class22, [{
+      key: 'where',
+      value: function where(condition) {
+        for (var _len8 = arguments.length, values = Array(_len8 > 1 ? _len8 - 1 : 0), _key8 = 1; _key8 < _len8; _key8++) {
+          values[_key8 - 1] = arguments[_key8];
+        }
+
+        this._condition.apply(this, [condition].concat(values));
+      }
+    }]);
+
+    return _class22;
+  }(cls.AbstractConditionBlock);
+
+  // HAVING
+  cls.HavingBlock = function (_cls$AbstractConditio2) {
+    _inherits(_class23, _cls$AbstractConditio2);
+
+    function _class23(options) {
+      _classCallCheck(this, _class23);
+
+      return _possibleConstructorReturn(this, Object.getPrototypeOf(_class23).call(this, _extend({}, options, {
+        verb: 'HAVING'
+      })));
+    }
+
+    _createClass(_class23, [{
+      key: 'having',
+      value: function having(condition) {
+        for (var _len9 = arguments.length, values = Array(_len9 > 1 ? _len9 - 1 : 0), _key9 = 1; _key9 < _len9; _key9++) {
+          values[_key9 - 1] = arguments[_key9];
+        }
+
+        this._condition.apply(this, [condition].concat(values));
+      }
+    }]);
+
+    return _class23;
+  }(cls.AbstractConditionBlock);
+
+  // ORDER BY
+  cls.OrderByBlock = function (_cls$Block11) {
+    _inherits(_class24, _cls$Block11);
+
+    function _class24(options) {
+      _classCallCheck(this, _class24);
+
+      var _this28 = _possibleConstructorReturn(this, Object.getPrototypeOf(_class24).call(this, options));
+
+      _this28._orders = [];
+      return _this28;
+    }
+
+    /**
+    # Add an ORDER BY transformation for the given field in the given order.
+    #
+    # To specify descending order pass false for the 'asc' parameter.
+    */
+
+
+    _createClass(_class24, [{
+      key: 'order',
+      value: function order(field, asc) {
+        for (var _len10 = arguments.length, values = Array(_len10 > 2 ? _len10 - 2 : 0), _key10 = 2; _key10 < _len10; _key10++) {
+          values[_key10 - 2] = arguments[_key10];
+        }
+
+        field = this._sanitizeField(field);
+
+        asc = asc === undefined ? true : asc;
+        asc = asc !== null ? !!asc : asc;
+
+        this._orders.push({
+          field: field,
+          dir: asc,
+          values: values
+        });
+      }
+    }, {
+      key: '_toParamString',
+      value: function _toParamString() {
+        var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+        var totalStr = '',
+            totalValues = [];
+
+        var _iteratorNormalCompletion11 = true;
+        var _didIteratorError11 = false;
+        var _iteratorError11 = undefined;
+
+        try {
+          for (var _iterator11 = this._orders[Symbol.iterator](), _step11; !(_iteratorNormalCompletion11 = (_step11 = _iterator11.next()).done); _iteratorNormalCompletion11 = true) {
+            var _step11$value = _step11.value;
+            var field = _step11$value.field;
+            var dir = _step11$value.dir;
+            var values = _step11$value.values;
+
+            totalStr = _pad(totalStr, ', ');
+
+            var ret = this._buildString(field, values, {
+              buildParameterized: options.buildParameterized
+            });
+
+            totalStr += ret.text, totalValues.push.apply(totalValues, _toConsumableArray(ret.values));
+
+            if (dir !== null) {
+              totalStr += ' ' + (dir ? 'ASC' : 'DESC');
+            }
+          }
+        } catch (err) {
+          _didIteratorError11 = true;
+          _iteratorError11 = err;
+        } finally {
+          try {
+            if (!_iteratorNormalCompletion11 && _iterator11.return) {
+              _iterator11.return();
+            }
+          } finally {
+            if (_didIteratorError11) {
+              throw _iteratorError11;
+            }
+          }
+        }
+
+        return {
+          text: totalStr.length ? 'ORDER BY ' + totalStr : '',
+          values: totalValues
+        };
+      }
+    }]);
+
+    return _class24;
+  }(cls.Block);
+
+  // LIMIT
+  cls.LimitBlock = function (_cls$Block12) {
+    _inherits(_class25, _cls$Block12);
+
+    function _class25(options) {
+      _classCallCheck(this, _class25);
+
+      var _this29 = _possibleConstructorReturn(this, Object.getPrototypeOf(_class25).call(this, options));
+
+      _this29._limit = null;
+      return _this29;
+    }
+
+    /**
+    # Set the LIMIT transformation.
+    #
+    # Call this will override the previously set limit for this query. Also note that Passing 0 for 'max' will remove
+    # the limit.
+    */
+
+
+    _createClass(_class25, [{
+      key: 'limit',
+      value: function limit(_limit) {
+        this._limit = this._sanitizeLimitOffset(_limit);
+      }
+    }, {
+      key: '_toParamString',
+      value: function _toParamString() {
+        return {
+          text: null !== this._limit ? 'LIMIT ' + this._limit : '',
+          values: []
+        };
+      }
+    }]);
+
+    return _class25;
+  }(cls.Block);
+
+  // JOIN
+  cls.JoinBlock = function (_cls$Block13) {
+    _inherits(_class26, _cls$Block13);
+
+    function _class26(options) {
+      _classCallCheck(this, _class26);
+
+      var _this30 = _possibleConstructorReturn(this, Object.getPrototypeOf(_class26).call(this, options));
+
+      _this30._joins = [];
+      return _this30;
+    }
+
+    /**
+    # Add a JOIN with the given table.
+    #
+    # 'table' is the name of the table to join with.
+    #
+    # 'alias' is an optional alias for the table name.
+    #
+    # 'condition' is an optional condition (containing an SQL expression) for the JOIN.
+    #
+    # 'type' must be either one of INNER, OUTER, LEFT or RIGHT. Default is 'INNER'.
+    #
+    */
+
+
+    _createClass(_class26, [{
+      key: 'join',
+      value: function join(table) {
+        var alias = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+        var condition = arguments.length <= 2 || arguments[2] === undefined ? null : arguments[2];
+        var type = arguments.length <= 3 || arguments[3] === undefined ? 'INNER' : arguments[3];
+
+        table = this._sanitizeTable(table, true);
+        alias = alias ? this._sanitizeTableAlias(alias) : alias;
+        condition = condition ? this._sanitizeExpression(condition) : condition;
+
+        this._joins.push({
+          type: type,
+          table: table,
+          alias: alias,
+          condition: condition
+        });
+      }
+    }, {
+      key: 'left_join',
+      value: function left_join(table) {
+        var alias = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+        var condition = arguments.length <= 2 || arguments[2] === undefined ? null : arguments[2];
+
+        this.join(table, alias, condition, 'LEFT');
+      }
+    }, {
+      key: 'right_join',
+      value: function right_join(table) {
+        var alias = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+        var condition = arguments.length <= 2 || arguments[2] === undefined ? null : arguments[2];
+
+        this.join(table, alias, condition, 'RIGHT');
+      }
+    }, {
+      key: 'outer_join',
+      value: function outer_join(table) {
+        var alias = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+        var condition = arguments.length <= 2 || arguments[2] === undefined ? null : arguments[2];
+
+        this.join(table, alias, condition, 'OUTER');
+      }
+    }, {
+      key: 'left_outer_join',
+      value: function left_outer_join(table) {
+        var alias = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+        var condition = arguments.length <= 2 || arguments[2] === undefined ? null : arguments[2];
+
+        this.join(table, alias, condition, 'LEFT OUTER');
+      }
+    }, {
+      key: 'full_join',
+      value: function full_join(table) {
+        var alias = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+        var condition = arguments.length <= 2 || arguments[2] === undefined ? null : arguments[2];
+
+        this.join(table, alias, condition, 'FULL');
+      }
+    }, {
+      key: 'cross_join',
+      value: function cross_join(table) {
+        var alias = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+        var condition = arguments.length <= 2 || arguments[2] === undefined ? null : arguments[2];
+
+        this.join(table, alias, condition, 'CROSS');
+      }
+    }, {
+      key: '_toParamString',
+      value: function _toParamString() {
+        var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+        var totalStr = "",
+            totalValues = [];
+
+        var _iteratorNormalCompletion12 = true;
+        var _didIteratorError12 = false;
+        var _iteratorError12 = undefined;
+
+        try {
+          for (var _iterator12 = this._joins[Symbol.iterator](), _step12; !(_iteratorNormalCompletion12 = (_step12 = _iterator12.next()).done); _iteratorNormalCompletion12 = true) {
+            var _step12$value = _step12.value;
+            var type = _step12$value.type;
+            var table = _step12$value.table;
+            var alias = _step12$value.alias;
+            var condition = _step12$value.condition;
+
+            totalStr = _pad(totalStr, this.options.separator);
+
+            var tableStr = void 0;
+
+            if (table instanceof cls.BaseBuilder) {
+              var ret = table._toParamString({
+                buildParameterized: options.buildParameterized,
+                nested: true
+              });
+
+              totalValues.push.apply(totalValues, _toConsumableArray(ret.values));
+              tableStr = ret.text;
+            } else {
+              tableStr = this._formatTableName(table);
+            }
+
+            totalStr += type + ' JOIN ' + tableStr;
+
+            if (alias) {
+              totalStr += ' ' + this._formatTableAlias(alias);
+            }
+
+            if (condition) {
+              totalStr += ' ON ';
+
+              var _ret4 = void 0;
+
+              if (condition instanceof cls.Expression) {
+                _ret4 = condition._toParamString({
+                  buildParameterized: options.buildParameterized
+                });
+              } else {
+                _ret4 = this._buildString(condition, [], {
+                  buildParameterized: options.buildParameterized
+                });
+              }
+
+              totalStr += this._applyNestingFormatting(_ret4.text);
+              totalValues.push.apply(totalValues, _toConsumableArray(_ret4.values));
+            }
+          }
+        } catch (err) {
+          _didIteratorError12 = true;
+          _iteratorError12 = err;
+        } finally {
+          try {
+            if (!_iteratorNormalCompletion12 && _iterator12.return) {
+              _iterator12.return();
+            }
+          } finally {
+            if (_didIteratorError12) {
+              throw _iteratorError12;
+            }
+          }
+        }
+
+        return {
+          text: totalStr,
+          values: totalValues
+        };
+      }
+    }]);
+
+    return _class26;
+  }(cls.Block);
+
+  // UNION
+  cls.UnionBlock = function (_cls$Block14) {
+    _inherits(_class27, _cls$Block14);
+
+    function _class27(options) {
+      _classCallCheck(this, _class27);
+
+      var _this31 = _possibleConstructorReturn(this, Object.getPrototypeOf(_class27).call(this, options));
+
+      _this31._unions = [];
+      return _this31;
+    }
+
+    /**
+    # Add a UNION with the given table/query.
+    #
+    # 'table' is the name of the table or query to union with.
+    #
+    # 'type' must be either one of UNION or UNION ALL.... Default is 'UNION'.
+    */
+
+
+    _createClass(_class27, [{
+      key: 'union',
+      value: function union(table) {
+        var type = arguments.length <= 1 || arguments[1] === undefined ? 'UNION' : arguments[1];
+
+        table = this._sanitizeTable(table);
+
+        this._unions.push({
+          type: type,
+          table: table
+        });
+      }
+
+      // Add a UNION ALL with the given table/query.
+
+    }, {
+      key: 'union_all',
+      value: function union_all(table) {
+        this.union(table, 'UNION ALL');
+      }
+    }, {
+      key: '_toParamString',
+      value: function _toParamString() {
+        var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+        var totalStr = '',
+            totalValues = [];
+
+        var _iteratorNormalCompletion13 = true;
+        var _didIteratorError13 = false;
+        var _iteratorError13 = undefined;
+
+        try {
+          for (var _iterator13 = this._unions[Symbol.iterator](), _step13; !(_iteratorNormalCompletion13 = (_step13 = _iterator13.next()).done); _iteratorNormalCompletion13 = true) {
+            var _step13$value = _step13.value;
+            var type = _step13$value.type;
+            var table = _step13$value.table;
+
+            totalStr = _pad(totalStr, this.options.separator);
+
+            var tableStr = void 0;
+
+            if (table instanceof cls.BaseBuilder) {
+              var ret = table._toParamString({
+                buildParameterized: options.buildParameterized,
+                nested: true
+              });
+
+              tableStr = ret.text;
+              totalValues.push.apply(totalValues, _toConsumableArray(ret.values));
+            } else {
+              totalStr = this._formatTableName(table);
+            }
+
+            totalStr += type + ' ' + tableStr;
+          }
+        } catch (err) {
+          _didIteratorError13 = true;
+          _iteratorError13 = err;
+        } finally {
+          try {
+            if (!_iteratorNormalCompletion13 && _iterator13.return) {
+              _iterator13.return();
+            }
+          } finally {
+            if (_didIteratorError13) {
+              throw _iteratorError13;
+            }
+          }
+        }
+
+        return {
+          text: totalStr,
+          values: totalValues
+        };
+      }
+    }]);
+
+    return _class27;
+  }(cls.Block);
+
+  /*
+  # ---------------------------------------------------------------------------------------------------------
+  # ---------------------------------------------------------------------------------------------------------
+  # Query builders
+  # ---------------------------------------------------------------------------------------------------------
+  # ---------------------------------------------------------------------------------------------------------
+  */
+
+  /**
+  # Query builder base class
+  #
+  # Note that the query builder does not check the final query string for correctness.
+  #
+  # All the build methods in this object return the object instance for chained method calling purposes.
+  */
+  cls.QueryBuilder = function (_cls$BaseBuilder4) {
+    _inherits(_class28, _cls$BaseBuilder4);
+
+    /**
+    # Constructor
+    #
+    # blocks - array of cls.BaseBuilderBlock instances to build the query with.
+    */
+
+    function _class28(options, blocks) {
+      _classCallCheck(this, _class28);
+
+      var _this32 = _possibleConstructorReturn(this, Object.getPrototypeOf(_class28).call(this, options));
+
+      _this32.blocks = blocks || [];
+
+      // Copy exposed methods into myself
+      var _iteratorNormalCompletion14 = true;
+      var _didIteratorError14 = false;
+      var _iteratorError14 = undefined;
+
+      try {
+        for (var _iterator14 = _this32.blocks[Symbol.iterator](), _step14; !(_iteratorNormalCompletion14 = (_step14 = _iterator14.next()).done); _iteratorNormalCompletion14 = true) {
+          var block = _step14.value;
+
+          var exposedMethods = block.exposedMethods();
+
+          for (var methodName in exposedMethods) {
+            var methodBody = exposedMethods[methodName];
+
+            if (undefined !== _this32[methodName]) {
+              throw new Error('Builder already has a builder method called: ' + methodName);
+            }
+
+            (function (block, name, body) {
+              _this32[name] = function () {
+                for (var _len11 = arguments.length, args = Array(_len11), _key11 = 0; _key11 < _len11; _key11++) {
+                  args[_key11] = arguments[_key11];
+                }
+
+                body.call.apply(body, [block].concat(args));
+
+                return _this32;
+              };
+            })(block, methodName, methodBody);
+          }
+        }
+      } catch (err) {
+        _didIteratorError14 = true;
+        _iteratorError14 = err;
+      } finally {
+        try {
+          if (!_iteratorNormalCompletion14 && _iterator14.return) {
+            _iterator14.return();
+          }
+        } finally {
+          if (_didIteratorError14) {
+            throw _iteratorError14;
+          }
+        }
+      }
+
+      return _this32;
+    }
+
+    /**
+    # Register a custom value handler for this query builder and all its contained blocks.
+    #
+    # Note: This will override any globally registered handler for this value type.
+    */
+
+
+    _createClass(_class28, [{
+      key: 'registerValueHandler',
+      value: function registerValueHandler(type, handler) {
+        var _iteratorNormalCompletion15 = true;
+        var _didIteratorError15 = false;
+        var _iteratorError15 = undefined;
+
+        try {
+          for (var _iterator15 = this.blocks[Symbol.iterator](), _step15; !(_iteratorNormalCompletion15 = (_step15 = _iterator15.next()).done); _iteratorNormalCompletion15 = true) {
+            var block = _step15.value;
+
+            block.registerValueHandler(type, handler);
+          }
+        } catch (err) {
+          _didIteratorError15 = true;
+          _iteratorError15 = err;
+        } finally {
+          try {
+            if (!_iteratorNormalCompletion15 && _iterator15.return) {
+              _iterator15.return();
+            }
+          } finally {
+            if (_didIteratorError15) {
+              throw _iteratorError15;
+            }
+          }
+        }
+
+        _get(Object.getPrototypeOf(_class28.prototype), 'registerValueHandler', this).call(this, type, handler);
+
+        return this;
+      }
+
+      /**
+      # Update query builder options
+      #
+      # This will update the options for all blocks too. Use this method with caution as it allows you to change the
+      # behaviour of your query builder mid-build.
+      */
+
+    }, {
+      key: 'updateOptions',
+      value: function updateOptions(options) {
+        this.options = _extend({}, this.options, options);
+
+        var _iteratorNormalCompletion16 = true;
+        var _didIteratorError16 = false;
+        var _iteratorError16 = undefined;
+
+        try {
+          for (var _iterator16 = this.blocks[Symbol.iterator](), _step16; !(_iteratorNormalCompletion16 = (_step16 = _iterator16.next()).done); _iteratorNormalCompletion16 = true) {
+            var block = _step16.value;
+
+            block.options = _extend({}, block.options, options);
+          }
+        } catch (err) {
+          _didIteratorError16 = true;
+          _iteratorError16 = err;
+        } finally {
+          try {
+            if (!_iteratorNormalCompletion16 && _iterator16.return) {
+              _iterator16.return();
+            }
+          } finally {
+            if (_didIteratorError16) {
+              throw _iteratorError16;
+            }
+          }
+        }
+      }
+
+      // Get the final fully constructed query param obj.
+
+    }, {
+      key: '_toParamString',
+      value: function _toParamString() {
+        var _this33 = this,
+            _ref2;
+
+        var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+        options = _extend({}, this.options, options);
+
+        var blockResults = this.blocks.map(function (b) {
+          return b._toParamString({
+            buildParameterized: options.buildParameterized,
+            queryBuilder: _this33
+          });
+        });
+
+        var blockTexts = blockResults.map(function (b) {
+          return b.text;
+        });
+        var blockValues = blockResults.map(function (b) {
+          return b.values;
+        });
+
+        var totalStr = blockTexts.filter(function (v) {
+          return 0 < v.length;
+        }).join(options.separator);
+
+        var totalValues = (_ref2 = []).concat.apply(_ref2, _toConsumableArray(blockValues));
+
+        if (!options.nested) {
+          if (options.numberedParameters) {
+            (function () {
+              var i = undefined !== options.numberedParametersStartAt ? options.numberedParametersStartAt : 1;
+
+              // construct regex for searching
+              var regex = options.parameterCharacter.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
+
+              totalStr = totalStr.replace(new RegExp(regex, 'g'), function () {
+                return '' + options.numberedParametersPrefix + i++;
+              });
+            })();
+          }
+        }
+
+        return {
+          text: this._applyNestingFormatting(totalStr, !!options.nested),
+          values: totalValues
+        };
+      }
+
+      // Deep clone
+
+    }, {
+      key: 'clone',
+      value: function clone() {
+        var blockClones = this.blocks.map(function (v) {
+          return v.clone();
+        });
+
+        return new this.constructor(this.options, blockClones);
+      }
+
+      // Get a specific block
+
+    }, {
+      key: 'getBlock',
+      value: function getBlock(blockType) {
+        var filtered = this.blocks.filter(function (b) {
+          return b instanceof blockType;
+        });
+
+        return filtered[0];
+      }
+    }]);
+
+    return _class28;
+  }(cls.BaseBuilder);
+
+  // SELECT query builder.
+  cls.Select = function (_cls$QueryBuilder) {
+    _inherits(_class29, _cls$QueryBuilder);
+
+    function _class29(options) {
+      var blocks = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+
+      _classCallCheck(this, _class29);
+
+      blocks = blocks || [new cls.StringBlock(options, 'SELECT'), new cls.FunctionBlock(options), new cls.DistinctBlock(options), new cls.GetFieldBlock(options), new cls.FromTableBlock(options), new cls.JoinBlock(options), new cls.WhereBlock(options), new cls.GroupByBlock(options), new cls.HavingBlock(options), new cls.OrderByBlock(options), new cls.LimitBlock(options), new cls.OffsetBlock(options), new cls.UnionBlock(options)];
+
+      return _possibleConstructorReturn(this, Object.getPrototypeOf(_class29).call(this, options, blocks));
+    }
+
+    return _class29;
+  }(cls.QueryBuilder);
+
+  // UPDATE query builder.
+  cls.Update = function (_cls$QueryBuilder2) {
+    _inherits(_class30, _cls$QueryBuilder2);
+
+    function _class30(options) {
+      var blocks = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+
+      _classCallCheck(this, _class30);
+
+      blocks = blocks || [new cls.StringBlock(options, 'UPDATE'), new cls.UpdateTableBlock(options), new cls.SetFieldBlock(options), new cls.WhereBlock(options), new cls.OrderByBlock(options), new cls.LimitBlock(options)];
+
+      return _possibleConstructorReturn(this, Object.getPrototypeOf(_class30).call(this, options, blocks));
+    }
+
+    return _class30;
+  }(cls.QueryBuilder);
+
+  // DELETE query builder.
+  cls.Delete = function (_cls$QueryBuilder3) {
+    _inherits(_class31, _cls$QueryBuilder3);
+
+    function _class31(options) {
+      var blocks = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+
+      _classCallCheck(this, _class31);
+
+      blocks = blocks || [new cls.StringBlock(options, 'DELETE'), new cls.TargetTableBlock(options), new cls.FromTableBlock(_extend({}, options, {
+        singleTable: true
+      })), new cls.JoinBlock(options), new cls.WhereBlock(options), new cls.OrderByBlock(options), new cls.LimitBlock(options)];
+
+      return _possibleConstructorReturn(this, Object.getPrototypeOf(_class31).call(this, options, blocks));
+    }
+
+    return _class31;
+  }(cls.QueryBuilder);
+
+  // An INSERT query builder.
+  cls.Insert = function (_cls$QueryBuilder4) {
+    _inherits(_class32, _cls$QueryBuilder4);
+
+    function _class32(options) {
+      var blocks = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+
+      _classCallCheck(this, _class32);
+
+      blocks = blocks || [new cls.StringBlock(options, 'INSERT'), new cls.IntoTableBlock(options), new cls.InsertFieldValueBlock(options), new cls.InsertFieldsFromQueryBlock(options)];
+
+      return _possibleConstructorReturn(this, Object.getPrototypeOf(_class32).call(this, options, blocks));
+    }
+
+    return _class32;
+  }(cls.QueryBuilder);
+
+  var _squel = {
+    VERSION: '5.3.3',
+    flavour: flavour,
+    expr: function expr(options) {
+      return new cls.Expression(options);
+    },
+    case: function _case(name, options) {
+      return new cls.Case(name, options);
+    },
+    select: function select(options, blocks) {
+      return new cls.Select(options, blocks);
+    },
+    update: function update(options, blocks) {
+      return new cls.Update(options, blocks);
+    },
+    insert: function insert(options, blocks) {
+      return new cls.Insert(options, blocks);
+    },
+    delete: function _delete(options, blocks) {
+      return new cls.Delete(options, blocks);
+    },
+    str: function str() {
+      var inst = new cls.FunctionBlock();
+      inst.function.apply(inst, arguments);
+      return inst;
+    },
+    registerValueHandler: cls.registerValueHandler
+  };
+
+  // aliases
+  _squel.remove = _squel.delete;
+
+  // classes
+  _squel.cls = cls;
+
+  return _squel;
+}
+
+/**
+# ---------------------------------------------------------------------------------------------------------
+# ---------------------------------------------------------------------------------------------------------
+# Exported instance (and for use by flavour definitions further down).
+# ---------------------------------------------------------------------------------------------------------
+# ---------------------------------------------------------------------------------------------------------
+*/
+
+var squel = _buildSquel();
+
+/**
+# ---------------------------------------------------------------------------------------------------------
+# ---------------------------------------------------------------------------------------------------------
+# Squel SQL flavours
+# ---------------------------------------------------------------------------------------------------------
+# ---------------------------------------------------------------------------------------------------------
+*/
+
+// Available flavours
+squel.flavours = {};
+
+// Setup Squel for a particular SQL flavour
+squel.useFlavour = function () {
+  var flavour = arguments.length <= 0 || arguments[0] === undefined ? null : arguments[0];
+
+  if (!flavour) {
+    return squel;
+  }
+
+  if (squel.flavours[flavour] instanceof Function) {
+    var s = _buildSquel(flavour);
+
+    squel.flavours[flavour].call(null, s);
+
+    // add in flavour methods
+    s.flavours = squel.flavours;
+    s.useFlavour = squel.useFlavour;
+
+    return s;
+  } else {
+    throw new Error('Flavour not available: ' + flavour);
+  }
+};
+return squel;
+}));

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
www/lib/squel/dist/squel-basic.min.js


+ 3761 - 0
www/lib/squel/dist/squel.js

@@ -0,0 +1,3761 @@
+;(function(root, factory) {
+  if (typeof define === 'function' && define.amd) {
+    define([], factory);
+  } else if (typeof exports === 'object') {
+    module.exports = factory();
+  } else {
+    root.squel = factory();
+  }
+}(this, function() {
+'use strict';
+
+var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } };
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; };
+
+function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
+
+function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+// append to string if non-empty
+function _pad(str, pad) {
+  return str.length ? str + pad : str;
+}
+
+// Extend given object's with other objects' properties, overriding existing ones if necessary
+function _extend(dst) {
+  for (var _len = arguments.length, sources = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
+    sources[_key - 1] = arguments[_key];
+  }
+
+  if (dst && sources) {
+    var _iteratorNormalCompletion = true;
+    var _didIteratorError = false;
+    var _iteratorError = undefined;
+
+    try {
+      var _loop = function _loop() {
+        var src = _step.value;
+
+        if ((typeof src === 'undefined' ? 'undefined' : _typeof(src)) === 'object') {
+          Object.getOwnPropertyNames(src).forEach(function (key) {
+            dst[key] = src[key];
+          });
+        }
+      };
+
+      for (var _iterator = sources[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
+        _loop();
+      }
+    } catch (err) {
+      _didIteratorError = true;
+      _iteratorError = err;
+    } finally {
+      try {
+        if (!_iteratorNormalCompletion && _iterator.return) {
+          _iterator.return();
+        }
+      } finally {
+        if (_didIteratorError) {
+          throw _iteratorError;
+        }
+      }
+    }
+  }
+
+  return dst;
+};
+
+// get whether object is a plain object
+function _isPlainObject(obj) {
+  return obj && obj.constructor.prototype === Object.prototype;
+};
+
+// get whether object is an array
+function _isArray(obj) {
+  return obj && obj.constructor.prototype === Array.prototype;
+};
+
+// get class name of given object
+function _getObjectClassName(obj) {
+  if (obj && obj.constructor && obj.constructor.toString) {
+    var arr = obj.constructor.toString().match(/function\s*(\w+)/);
+
+    if (arr && 2 === arr.length) {
+      return arr[1];
+    }
+  }
+}
+
+// clone given item
+function _clone(src) {
+  if (!src) {
+    return src;
+  }
+
+  if (typeof src.clone === 'function') {
+    return src.clone();
+  } else if (_isPlainObject(src) || _isArray(src)) {
+    var _ret2 = function () {
+      var ret = new src.constructor();
+
+      Object.getOwnPropertyNames(src).forEach(function (key) {
+        if (typeof src[key] !== 'function') {
+          ret[key] = _clone(src[key]);
+        }
+      });
+
+      return {
+        v: ret
+      };
+    }();
+
+    if ((typeof _ret2 === 'undefined' ? 'undefined' : _typeof(_ret2)) === "object") return _ret2.v;
+  } else {
+    return JSON.parse(JSON.stringify(src));
+  }
+};
+
+/**
+ * Register a value type handler
+ *
+ * Note: this will override any existing handler registered for this value type.
+ */
+function _registerValueHandler(handlers, type, handler) {
+  var typeofType = typeof type === 'undefined' ? 'undefined' : _typeof(type);
+
+  if (typeofType !== 'function' && typeofType !== 'string') {
+    throw new Error("type must be a class constructor or string");
+  }
+
+  if (typeof handler !== 'function') {
+    throw new Error("handler must be a function");
+  }
+
+  var _iteratorNormalCompletion2 = true;
+  var _didIteratorError2 = false;
+  var _iteratorError2 = undefined;
+
+  try {
+    for (var _iterator2 = handlers[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
+      var typeHandler = _step2.value;
+
+      if (typeHandler.type === type) {
+        typeHandler.handler = handler;
+
+        return;
+      }
+    }
+  } catch (err) {
+    _didIteratorError2 = true;
+    _iteratorError2 = err;
+  } finally {
+    try {
+      if (!_iteratorNormalCompletion2 && _iterator2.return) {
+        _iterator2.return();
+      }
+    } finally {
+      if (_didIteratorError2) {
+        throw _iteratorError2;
+      }
+    }
+  }
+
+  handlers.push({
+    type: type,
+    handler: handler
+  });
+};
+
+/**
+ * Get value type handler for given type
+ */
+function getValueHandler(value) {
+  for (var _len2 = arguments.length, handlerLists = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
+    handlerLists[_key2 - 1] = arguments[_key2];
+  }
+
+  var _iteratorNormalCompletion3 = true;
+  var _didIteratorError3 = false;
+  var _iteratorError3 = undefined;
+
+  try {
+    for (var _iterator3 = handlerLists[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
+      var handlers = _step3.value;
+      var _iteratorNormalCompletion4 = true;
+      var _didIteratorError4 = false;
+      var _iteratorError4 = undefined;
+
+      try {
+        for (var _iterator4 = handlers[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) {
+          var typeHandler = _step4.value;
+
+          // if type is a string then use `typeof` or else use `instanceof`
+          if ((typeof value === 'undefined' ? 'undefined' : _typeof(value)) === typeHandler.type || typeof typeHandler.type !== 'string' && value instanceof typeHandler.type) {
+            return typeHandler.handler;
+          }
+        }
+      } catch (err) {
+        _didIteratorError4 = true;
+        _iteratorError4 = err;
+      } finally {
+        try {
+          if (!_iteratorNormalCompletion4 && _iterator4.return) {
+            _iterator4.return();
+          }
+        } finally {
+          if (_didIteratorError4) {
+            throw _iteratorError4;
+          }
+        }
+      }
+    }
+  } catch (err) {
+    _didIteratorError3 = true;
+    _iteratorError3 = err;
+  } finally {
+    try {
+      if (!_iteratorNormalCompletion3 && _iterator3.return) {
+        _iterator3.return();
+      }
+    } finally {
+      if (_didIteratorError3) {
+        throw _iteratorError3;
+      }
+    }
+  }
+};
+
+/**
+ * Build base squel classes and methods
+ */
+function _buildSquel() {
+  var flavour = arguments.length <= 0 || arguments[0] === undefined ? null : arguments[0];
+
+  var cls = {
+    _getObjectClassName: _getObjectClassName
+  };
+
+  // default query builder options
+  cls.DefaultQueryBuilderOptions = {
+    // If true then table names will be rendered inside quotes. The quote character used is configurable via the nameQuoteCharacter option.
+    autoQuoteTableNames: false,
+    // If true then field names will rendered inside quotes. The quote character used is configurable via the nameQuoteCharacter option.
+    autoQuoteFieldNames: false,
+    // If true then alias names will rendered inside quotes. The quote character used is configurable via the `tableAliasQuoteCharacter` and `fieldAliasQuoteCharacter` options.
+    autoQuoteAliasNames: true,
+    // If true then table alias names will rendered after AS keyword.
+    useAsForTableAliasNames: false,
+    // The quote character used for when quoting table and field names
+    nameQuoteCharacter: '`',
+    // The quote character used for when quoting table alias names
+    tableAliasQuoteCharacter: '`',
+    // The quote character used for when quoting table alias names
+    fieldAliasQuoteCharacter: '"',
+    // Custom value handlers where key is the value type and the value is the handler function
+    valueHandlers: [],
+    // Character used to represent a parameter value
+    parameterCharacter: '?',
+    // Numbered parameters returned from toParam() as $1, $2, etc.
+    numberedParameters: false,
+    // Numbered parameters prefix character(s)
+    numberedParametersPrefix: '$',
+    // Numbered parameters start at this number.
+    numberedParametersStartAt: 1,
+    // If true then replaces all single quotes within strings. The replacement string used is configurable via the `singleQuoteReplacement` option.
+    replaceSingleQuotes: false,
+    // The string to replace single quotes with in query strings
+    singleQuoteReplacement: '\'\'',
+    // String used to join individual blocks in a query when it's stringified
+    separator: ' ',
+    // Function for formatting string values prior to insertion into query string
+    stringFormatter: null
+  };
+
+  // Global custom value handlers for all instances of builder
+  cls.globalValueHandlers = [];
+
+  /*
+  # ---------------------------------------------------------------------------------------------------------
+  # ---------------------------------------------------------------------------------------------------------
+  # Custom value types
+  # ---------------------------------------------------------------------------------------------------------
+  # ---------------------------------------------------------------------------------------------------------
+   */
+
+  // Register a new value handler
+  cls.registerValueHandler = function (type, handler) {
+    _registerValueHandler(cls.globalValueHandlers, type, handler);
+  };
+
+  /*
+  # ---------------------------------------------------------------------------------------------------------
+  # ---------------------------------------------------------------------------------------------------------
+  # Base classes
+  # ---------------------------------------------------------------------------------------------------------
+  # ---------------------------------------------------------------------------------------------------------
+  */
+
+  // Base class for cloneable builders
+  cls.Cloneable = function () {
+    function _class() {
+      _classCallCheck(this, _class);
+    }
+
+    _createClass(_class, [{
+      key: 'clone',
+
+      /**
+       * Clone this builder
+       */
+      value: function clone() {
+        var newInstance = new this.constructor();
+
+        return _extend(newInstance, _clone(_extend({}, this)));
+      }
+    }]);
+
+    return _class;
+  }();
+
+  // Base class for all builders
+  cls.BaseBuilder = function (_cls$Cloneable) {
+    _inherits(_class2, _cls$Cloneable);
+
+    /**
+     * Constructor.
+     * this.param  {Object} options Overriding one or more of `cls.DefaultQueryBuilderOptions`.
+     */
+
+    function _class2(options) {
+      _classCallCheck(this, _class2);
+
+      var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(_class2).call(this));
+
+      var defaults = JSON.parse(JSON.stringify(cls.DefaultQueryBuilderOptions));
+
+      _this.options = _extend({}, defaults, options);
+      return _this;
+    }
+
+    /**
+     * Register a custom value handler for this builder instance.
+     *
+     * Note: this will override any globally registered handler for this value type.
+     */
+
+
+    _createClass(_class2, [{
+      key: 'registerValueHandler',
+      value: function registerValueHandler(type, handler) {
+        _registerValueHandler(this.options.valueHandlers, type, handler);
+
+        return this;
+      }
+
+      /**
+       * Sanitize given expression.
+       */
+
+    }, {
+      key: '_sanitizeExpression',
+      value: function _sanitizeExpression(expr) {
+        // If it's not an Expression builder instance
+        if (!(expr instanceof cls.Expression)) {
+          // It must then be a string
+          if (typeof expr !== "string") {
+            throw new Error("expression must be a string or Expression instance");
+          }
+        }
+
+        return expr;
+      }
+
+      /**
+       * Sanitize the given name.
+       *
+       * The 'type' parameter is used to construct a meaningful error message in case validation fails.
+       */
+
+    }, {
+      key: '_sanitizeName',
+      value: function _sanitizeName(value, type) {
+        if (typeof value !== "string") {
+          throw new Error(type + ' must be a string');
+        }
+
+        return value;
+      }
+    }, {
+      key: '_sanitizeField',
+      value: function _sanitizeField(item) {
+        if (!(item instanceof cls.BaseBuilder)) {
+          item = this._sanitizeName(item, "field name");
+        }
+
+        return item;
+      }
+    }, {
+      key: '_sanitizeBaseBuilder',
+      value: function _sanitizeBaseBuilder(item) {
+        if (item instanceof cls.BaseBuilder) {
+          return item;
+        }
+
+        throw new Error("must be a BaseBuilder instance");
+      }
+    }, {
+      key: '_sanitizeTable',
+      value: function _sanitizeTable(item) {
+        if (typeof item !== "string") {
+          try {
+            item = this._sanitizeBaseBuilder(item);
+          } catch (e) {
+            throw new Error("table name must be a string or a query builder");
+          }
+        } else {
+          item = this._sanitizeName(item, 'table');
+        }
+
+        return item;
+      }
+    }, {
+      key: '_sanitizeTableAlias',
+      value: function _sanitizeTableAlias(item) {
+        return this._sanitizeName(item, "table alias");
+      }
+    }, {
+      key: '_sanitizeFieldAlias',
+      value: function _sanitizeFieldAlias(item) {
+        return this._sanitizeName(item, "field alias");
+      }
+
+      // Sanitize the given limit/offset value.
+
+    }, {
+      key: '_sanitizeLimitOffset',
+      value: function _sanitizeLimitOffset(value) {
+        value = parseInt(value);
+
+        if (0 > value || isNaN(value)) {
+          throw new Error("limit/offset must be >= 0");
+        }
+
+        return value;
+      }
+
+      // Santize the given field value
+
+    }, {
+      key: '_sanitizeValue',
+      value: function _sanitizeValue(item) {
+        var itemType = typeof item === 'undefined' ? 'undefined' : _typeof(item);
+
+        if (null === item) {
+          // null is allowed
+        } else if ("string" === itemType || "number" === itemType || "boolean" === itemType) {
+            // primitives are allowed
+          } else if (item instanceof cls.BaseBuilder) {
+              // Builders allowed
+            } else {
+                var typeIsValid = !!getValueHandler(item, this.options.valueHandlers, cls.globalValueHandlers);
+
+                if (!typeIsValid) {
+                  throw new Error("field value must be a string, number, boolean, null or one of the registered custom value types");
+                }
+              }
+
+        return item;
+      }
+
+      // Escape a string value, e.g. escape quotes and other characters within it.
+
+    }, {
+      key: '_escapeValue',
+      value: function _escapeValue(value) {
+        return !this.options.replaceSingleQuotes ? value : value.replace(/\'/g, this.options.singleQuoteReplacement);
+      }
+    }, {
+      key: '_formatTableName',
+      value: function _formatTableName(item) {
+        if (this.options.autoQuoteTableNames) {
+          var quoteChar = this.options.nameQuoteCharacter;
+
+          item = '' + quoteChar + item + quoteChar;
+        }
+
+        return item;
+      }
+    }, {
+      key: '_formatFieldAlias',
+      value: function _formatFieldAlias(item) {
+        if (this.options.autoQuoteAliasNames) {
+          var quoteChar = this.options.fieldAliasQuoteCharacter;
+
+          item = '' + quoteChar + item + quoteChar;
+        }
+
+        return item;
+      }
+    }, {
+      key: '_formatTableAlias',
+      value: function _formatTableAlias(item) {
+        if (this.options.autoQuoteAliasNames) {
+          var quoteChar = this.options.tableAliasQuoteCharacter;
+
+          item = '' + quoteChar + item + quoteChar;
+        }
+
+        return this.options.useAsForTableAliasNames ? 'AS ' + item : item;
+      }
+    }, {
+      key: '_formatFieldName',
+      value: function _formatFieldName(item) {
+        var _this2 = this;
+
+        var formattingOptions = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
+
+        if (this.options.autoQuoteFieldNames) {
+          (function () {
+            var quoteChar = _this2.options.nameQuoteCharacter;
+
+            if (formattingOptions.ignorePeriodsForFieldNameQuotes) {
+              // a.b.c -> `a.b.c`
+              item = '' + quoteChar + item + quoteChar;
+            } else {
+              // a.b.c -> `a`.`b`.`c`
+              item = item.split('.').map(function (v) {
+                // treat '*' as special case (#79)
+                return '*' === v ? v : '' + quoteChar + v + quoteChar;
+              }).join('.');
+            }
+          })();
+        }
+
+        return item;
+      }
+
+      // Format the given custom value
+
+    }, {
+      key: '_formatCustomValue',
+      value: function _formatCustomValue(value) {
+        var asParam = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1];
+
+        // user defined custom handlers takes precedence
+        var customHandler = getValueHandler(value, this.options.valueHandlers, cls.globalValueHandlers);
+
+        // use the custom handler if available
+        if (customHandler) {
+          value = customHandler(value, asParam);
+        }
+
+        return value;
+      }
+
+      /** 
+       * Format given value for inclusion into parameter values array.
+       */
+
+    }, {
+      key: '_formatValueForParamArray',
+      value: function _formatValueForParamArray(value) {
+        var _this3 = this;
+
+        if (_isArray(value)) {
+          return value.map(function (v) {
+            return _this3._formatValueForParamArray(v);
+          });
+        } else {
+          return this._formatCustomValue(value, true);
+        }
+      }
+
+      /**
+       * Format the given field value for inclusion into the query string
+       */
+
+    }, {
+      key: '_formatValueForQueryString',
+      value: function _formatValueForQueryString(value) {
+        var _this4 = this;
+
+        var formattingOptions = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
+
+        var customFormattedValue = this._formatCustomValue(value);
+
+        // if formatting took place then return it directly
+        if (customFormattedValue !== value) {
+          return this._applyNestingFormatting(customFormattedValue);
+        }
+
+        // if it's an array then format each element separately
+        if (_isArray(value)) {
+          value = value.map(function (v) {
+            return _this4._formatValueForQueryString(v);
+          });
+
+          value = this._applyNestingFormatting(value.join(', '));
+        } else {
+          var typeofValue = typeof value === 'undefined' ? 'undefined' : _typeof(value);
+
+          if (null === value) {
+            value = "NULL";
+          } else if (typeofValue === "boolean") {
+            value = value ? "TRUE" : "FALSE";
+          } else if (value instanceof cls.BaseBuilder) {
+            value = this._applyNestingFormatting(value.toString());
+          } else if (typeofValue !== "number") {
+            // if it's a string and we have custom string formatting turned on then use that
+            if ('string' === typeofValue && this.options.stringFormatter) {
+              return this.options.stringFormatter(value);
+            }
+
+            if (formattingOptions.dontQuote) {
+              value = '' + value;
+            } else {
+              var escapedValue = this._escapeValue(value);
+
+              value = '\'' + escapedValue + '\'';
+            }
+          }
+        }
+
+        return value;
+      }
+    }, {
+      key: '_applyNestingFormatting',
+      value: function _applyNestingFormatting(str) {
+        var nesting = arguments.length <= 1 || arguments[1] === undefined ? true : arguments[1];
+
+        if (str && typeof str === 'string' && nesting) {
+          // don't want to apply twice
+          if ('(' !== str.charAt(0) || ')' !== str.charAt(str.length - 1)) {
+            return '(' + str + ')';
+          }
+        }
+
+        return str;
+      }
+
+      /** 
+       * Build given string and its corresponding parameter values into 
+       * output.
+       * 
+       * @param {String} str
+       * @param {Array}  values
+       * @param {Object} [options] Additional options.
+       * @param {Boolean} [options.buildParameterized] Whether to build paramterized string. Default is false.
+       * @param {Boolean} [options.nested] Whether this expression is nested within another.
+       * @param {Boolean} [options.formattingOptions] Formatting options for values in query string.
+       * @return {Object}
+       */
+
+    }, {
+      key: '_buildString',
+      value: function _buildString(str, values) {
+        var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2];
+        var nested = options.nested;
+        var buildParameterized = options.buildParameterized;
+        var formattingOptions = options.formattingOptions;
+
+
+        values = values || [];
+        str = str || '';
+
+        var formattedStr = '',
+            curValue = -1,
+            formattedValues = [];
+
+        var paramChar = this.options.parameterCharacter;
+
+        var idx = 0;
+
+        while (str.length > idx) {
+          // param char?
+          if (str.substr(idx, paramChar.length) === paramChar) {
+            var value = values[++curValue];
+
+            if (buildParameterized) {
+              if (value instanceof cls.BaseBuilder) {
+                var ret = value._toParamString({
+                  buildParameterized: buildParameterized,
+                  nested: true
+                });
+
+                formattedStr += ret.text;
+                formattedValues.push.apply(formattedValues, _toConsumableArray(ret.values));
+              } else {
+                value = this._formatValueForParamArray(value);
+
+                if (_isArray(value)) {
+                  // Array(6) -> "(??, ??, ??, ??, ??, ??)"
+                  var tmpStr = value.map(function () {
+                    return paramChar;
+                  }).join(', ');
+
+                  formattedStr += '(' + tmpStr + ')';
+
+                  formattedValues.push.apply(formattedValues, _toConsumableArray(value));
+                } else {
+                  formattedStr += paramChar;
+
+                  formattedValues.push(value);
+                }
+              }
+            } else {
+              formattedStr += this._formatValueForQueryString(value, formattingOptions);
+            }
+
+            idx += paramChar.length;
+          } else {
+            formattedStr += str.charAt(idx);
+
+            idx++;
+          }
+        }
+
+        return {
+          text: this._applyNestingFormatting(formattedStr, !!nested),
+          values: formattedValues
+        };
+      }
+
+      /** 
+       * Build all given strings and their corresponding parameter values into 
+       * output.
+       * 
+       * @param {Array} strings
+       * @param {Array}  strValues array of value arrays corresponding to each string.
+       * @param {Object} [options] Additional options.
+       * @param {Boolean} [options.buildParameterized] Whether to build paramterized string. Default is false.
+       * @param {Boolean} [options.nested] Whether this expression is nested within another.
+       * @return {Object}
+       */
+
+    }, {
+      key: '_buildManyStrings',
+      value: function _buildManyStrings(strings, strValues) {
+        var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2];
+
+        var totalStr = [],
+            totalValues = [];
+
+        for (var idx = 0; strings.length > idx; ++idx) {
+          var inputString = strings[idx],
+              inputValues = strValues[idx];
+
+          var _buildString2 = this._buildString(inputString, inputValues, {
+            buildParameterized: options.buildParameterized,
+            nested: false
+          });
+
+          var text = _buildString2.text;
+          var values = _buildString2.values;
+
+
+          totalStr.push(text);
+          totalValues.push.apply(totalValues, _toConsumableArray(values));
+        }
+
+        totalStr = totalStr.join(this.options.separator);
+
+        return {
+          text: totalStr.length ? this._applyNestingFormatting(totalStr, !!options.nested) : '',
+          values: totalValues
+        };
+      }
+
+      /**
+       * Get parameterized representation of this instance.
+       * 
+       * @param {Object} [options] Options.
+       * @param {Boolean} [options.buildParameterized] Whether to build paramterized string. Default is false.
+       * @param {Boolean} [options.nested] Whether this expression is nested within another.
+       * @return {Object}
+       */
+
+    }, {
+      key: '_toParamString',
+      value: function _toParamString(options) {
+        throw new Error('Not yet implemented');
+      }
+
+      /**
+       * Get the expression string.
+       * @return {String}
+       */
+
+    }, {
+      key: 'toString',
+      value: function toString() {
+        var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+        return this._toParamString(options).text;
+      }
+
+      /**
+       * Get the parameterized expression string.
+       * @return {Object}
+       */
+
+    }, {
+      key: 'toParam',
+      value: function toParam() {
+        var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+        return this._toParamString(_extend({}, options, {
+          buildParameterized: true
+        }));
+      }
+    }]);
+
+    return _class2;
+  }(cls.Cloneable);
+
+  /*
+  # ---------------------------------------------------------------------------------------------------------
+  # ---------------------------------------------------------------------------------------------------------
+  # cls.Expressions
+  # ---------------------------------------------------------------------------------------------------------
+  # ---------------------------------------------------------------------------------------------------------
+  */
+
+  /**
+   * An SQL expression builder.
+   *
+   * SQL expressions are used in WHERE and ON clauses to filter data by various criteria.
+   *
+   * Expressions can be nested. Nested expression contains can themselves 
+   * contain nested expressions. When rendered a nested expression will be 
+   * fully contained within brackets.
+   * 
+   * All the build methods in this object return the object instance for chained method calling purposes.
+   */
+  cls.Expression = function (_cls$BaseBuilder) {
+    _inherits(_class3, _cls$BaseBuilder);
+
+    // Initialise the expression.
+
+    function _class3(options) {
+      _classCallCheck(this, _class3);
+
+      var _this5 = _possibleConstructorReturn(this, Object.getPrototypeOf(_class3).call(this, options));
+
+      _this5._nodes = [];
+      return _this5;
+    }
+
+    // Combine the current expression with the given expression using the intersection operator (AND).
+
+
+    _createClass(_class3, [{
+      key: 'and',
+      value: function and(expr) {
+        for (var _len3 = arguments.length, params = Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) {
+          params[_key3 - 1] = arguments[_key3];
+        }
+
+        expr = this._sanitizeExpression(expr);
+
+        this._nodes.push({
+          type: 'AND',
+          expr: expr,
+          para: params
+        });
+
+        return this;
+      }
+
+      // Combine the current expression with the given expression using the union operator (OR).
+
+    }, {
+      key: 'or',
+      value: function or(expr) {
+        for (var _len4 = arguments.length, params = Array(_len4 > 1 ? _len4 - 1 : 0), _key4 = 1; _key4 < _len4; _key4++) {
+          params[_key4 - 1] = arguments[_key4];
+        }
+
+        expr = this._sanitizeExpression(expr);
+
+        this._nodes.push({
+          type: 'OR',
+          expr: expr,
+          para: params
+        });
+
+        return this;
+      }
+    }, {
+      key: '_toParamString',
+      value: function _toParamString() {
+        var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+        var totalStr = [],
+            totalValues = [];
+
+        var _iteratorNormalCompletion5 = true;
+        var _didIteratorError5 = false;
+        var _iteratorError5 = undefined;
+
+        try {
+          for (var _iterator5 = this._nodes[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) {
+            var node = _step5.value;
+            var type = node.type;
+            var expr = node.expr;
+            var para = node.para;
+
+            var _ref = expr instanceof cls.Expression ? expr._toParamString({
+              buildParameterized: options.buildParameterized,
+              nested: true
+            }) : this._buildString(expr, para, {
+              buildParameterized: options.buildParameterized
+            });
+
+            var text = _ref.text;
+            var values = _ref.values;
+
+
+            if (totalStr.length) {
+              totalStr.push(type);
+            }
+
+            totalStr.push(text);
+            totalValues.push.apply(totalValues, _toConsumableArray(values));
+          }
+        } catch (err) {
+          _didIteratorError5 = true;
+          _iteratorError5 = err;
+        } finally {
+          try {
+            if (!_iteratorNormalCompletion5 && _iterator5.return) {
+              _iterator5.return();
+            }
+          } finally {
+            if (_didIteratorError5) {
+              throw _iteratorError5;
+            }
+          }
+        }
+
+        totalStr = totalStr.join(' ');
+
+        return {
+          text: this._applyNestingFormatting(totalStr, !!options.nested),
+          values: totalValues
+        };
+      }
+    }]);
+
+    return _class3;
+  }(cls.BaseBuilder);
+
+  /*
+  # ---------------------------------------------------------------------------------------------------------
+  # ---------------------------------------------------------------------------------------------------------
+  # cls.Case
+  # ---------------------------------------------------------------------------------------------------------
+  # ---------------------------------------------------------------------------------------------------------
+  */
+
+  /**
+   * An SQL CASE expression builder.
+   *
+   * SQL cases are used to select proper values based on specific criteria.
+   */
+  cls.Case = function (_cls$BaseBuilder2) {
+    _inherits(_class4, _cls$BaseBuilder2);
+
+    function _class4(fieldName) {
+      var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
+
+      _classCallCheck(this, _class4);
+
+      var _this6 = _possibleConstructorReturn(this, Object.getPrototypeOf(_class4).call(this, options));
+
+      if (_isPlainObject(fieldName)) {
+        options = fieldName;
+
+        fieldName = null;
+      }
+
+      if (fieldName) {
+        _this6._fieldName = _this6._sanitizeField(fieldName);
+      }
+
+      _this6.options = _extend({}, cls.DefaultQueryBuilderOptions, options);
+
+      _this6._cases = [];
+      _this6._elseValue = null;
+      return _this6;
+    }
+
+    _createClass(_class4, [{
+      key: 'when',
+      value: function when(expression) {
+        for (var _len5 = arguments.length, values = Array(_len5 > 1 ? _len5 - 1 : 0), _key5 = 1; _key5 < _len5; _key5++) {
+          values[_key5 - 1] = arguments[_key5];
+        }
+
+        this._cases.unshift({
+          expression: expression,
+          values: values
+        });
+
+        return this;
+      }
+    }, {
+      key: 'then',
+      value: function then(result) {
+        if (this._cases.length == 0) {
+          throw new Error("when() needs to be called first");
+        }
+
+        this._cases[0].result = result;
+
+        return this;
+      }
+    }, {
+      key: 'else',
+      value: function _else(elseValue) {
+        this._elseValue = elseValue;
+
+        return this;
+      }
+    }, {
+      key: '_toParamString',
+      value: function _toParamString() {
+        var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+        var totalStr = '',
+            totalValues = [];
+
+        var _iteratorNormalCompletion6 = true;
+        var _didIteratorError6 = false;
+        var _iteratorError6 = undefined;
+
+        try {
+          for (var _iterator6 = this._cases[Symbol.iterator](), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) {
+            var _step6$value = _step6.value;
+            var expression = _step6$value.expression;
+            var values = _step6$value.values;
+            var result = _step6$value.result;
+
+            totalStr = _pad(totalStr, ' ');
+
+            var ret = this._buildString(expression, values, {
+              buildParameterized: options.buildParameterized,
+              nested: true
+            });
+
+            totalStr += 'WHEN ' + ret.text + ' THEN ' + this._formatValueForQueryString(result);
+            totalValues.push.apply(totalValues, _toConsumableArray(ret.values));
+          }
+        } catch (err) {
+          _didIteratorError6 = true;
+          _iteratorError6 = err;
+        } finally {
+          try {
+            if (!_iteratorNormalCompletion6 && _iterator6.return) {
+              _iterator6.return();
+            }
+          } finally {
+            if (_didIteratorError6) {
+              throw _iteratorError6;
+            }
+          }
+        }
+
+        if (totalStr.length) {
+          totalStr += ' ELSE ' + this._formatValueForQueryString(this._elseValue) + ' END';
+
+          if (this._fieldName) {
+            totalStr = this._fieldName + ' ' + totalStr;
+          }
+
+          totalStr = 'CASE ' + totalStr;
+        } else {
+          totalStr = this._formatValueForQueryString(this._elseValue);
+        }
+
+        return {
+          text: totalStr,
+          values: totalValues
+        };
+      }
+    }]);
+
+    return _class4;
+  }(cls.BaseBuilder);
+
+  /*
+  # ---------------------------------------------------------------------------------------------------------
+  # ---------------------------------------------------------------------------------------------------------
+  # Building blocks
+  # ---------------------------------------------------------------------------------------------------------
+  # ---------------------------------------------------------------------------------------------------------
+  */
+
+  /*
+  # A building block represents a single build-step within a query building process.
+  #
+  # Query builders consist of one or more building blocks which get run in a particular order. Building blocks can
+  # optionally specify methods to expose through the query builder interface. They can access all the input data for
+  # the query builder and manipulate it as necessary, as well as append to the final query string output.
+  #
+  # If you wish to customize how queries get built or add proprietary query phrases and content then it is recommended
+  # that you do so using one or more custom building blocks.
+  #
+  # Original idea posted in https://github.com/hiddentao/export/issues/10#issuecomment-15016427
+  */
+  cls.Block = function (_cls$BaseBuilder3) {
+    _inherits(_class5, _cls$BaseBuilder3);
+
+    function _class5(options) {
+      _classCallCheck(this, _class5);
+
+      return _possibleConstructorReturn(this, Object.getPrototypeOf(_class5).call(this, options));
+    }
+
+    /**
+    # Get input methods to expose within the query builder.
+    #
+    # By default all methods except the following get returned:
+    #   methods prefixed with _
+    #   constructor and toString()
+    #
+    # @return Object key -> function pairs
+    */
+
+
+    _createClass(_class5, [{
+      key: 'exposedMethods',
+      value: function exposedMethods() {
+        var ret = {};
+
+        var obj = this;
+
+        while (obj) {
+          Object.getOwnPropertyNames(obj).forEach(function (prop) {
+            if ('constructor' !== prop && typeof obj[prop] === "function" && prop.charAt(0) !== '_' && !cls.Block.prototype[prop]) {
+              ret[prop] = obj[prop];
+            }
+          });
+
+          obj = Object.getPrototypeOf(obj);
+        };
+
+        return ret;
+      }
+    }]);
+
+    return _class5;
+  }(cls.BaseBuilder);
+
+  // A fixed string which always gets output
+  cls.StringBlock = function (_cls$Block) {
+    _inherits(_class6, _cls$Block);
+
+    function _class6(options, str) {
+      _classCallCheck(this, _class6);
+
+      var _this8 = _possibleConstructorReturn(this, Object.getPrototypeOf(_class6).call(this, options));
+
+      _this8._str = str;
+      return _this8;
+    }
+
+    _createClass(_class6, [{
+      key: '_toParamString',
+      value: function _toParamString() {
+        var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+        return {
+          text: this._str,
+          values: []
+        };
+      }
+    }]);
+
+    return _class6;
+  }(cls.Block);
+
+  // A function string block
+  cls.FunctionBlock = function (_cls$Block2) {
+    _inherits(_class7, _cls$Block2);
+
+    function _class7(options) {
+      _classCallCheck(this, _class7);
+
+      var _this9 = _possibleConstructorReturn(this, Object.getPrototypeOf(_class7).call(this, options));
+
+      _this9._strings = [];
+      _this9._values = [];
+      return _this9;
+    }
+
+    _createClass(_class7, [{
+      key: 'function',
+      value: function _function(str) {
+        this._strings.push(str);
+
+        for (var _len6 = arguments.length, values = Array(_len6 > 1 ? _len6 - 1 : 0), _key6 = 1; _key6 < _len6; _key6++) {
+          values[_key6 - 1] = arguments[_key6];
+        }
+
+        this._values.push(values);
+      }
+    }, {
+      key: '_toParamString',
+      value: function _toParamString() {
+        var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+        return this._buildManyStrings(this._strings, this._values, options);
+      }
+    }]);
+
+    return _class7;
+  }(cls.Block);
+
+  // value handler for FunctionValueBlock objects
+  cls.registerValueHandler(cls.FunctionBlock, function (value) {
+    var asParam = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1];
+
+    return asParam ? value.toParam() : value.toString();
+  });
+
+  /*
+  # Table specifier base class
+  */
+  cls.AbstractTableBlock = function (_cls$Block3) {
+    _inherits(_class8, _cls$Block3);
+
+    /**
+     * @param {Boolean} [options.singleTable] If true then only allow one table spec.
+     * @param {String} [options.prefix] String prefix for output.
+     */
+
+    function _class8(options, prefix) {
+      _classCallCheck(this, _class8);
+
+      var _this10 = _possibleConstructorReturn(this, Object.getPrototypeOf(_class8).call(this, options));
+
+      _this10._tables = [];
+      return _this10;
+    }
+
+    /**
+    # Update given table.
+    #
+    # An alias may also be specified for the table.
+    #
+    # Concrete subclasses should provide a method which calls this
+    */
+
+
+    _createClass(_class8, [{
+      key: '_table',
+      value: function _table(table) {
+        var alias = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+
+        alias = alias ? this._sanitizeTableAlias(alias) : alias;
+        table = this._sanitizeTable(table);
+
+        if (this.options.singleTable) {
+          this._tables = [];
+        }
+
+        this._tables.push({
+          table: table,
+          alias: alias
+        });
+      }
+
+      // get whether a table has been set
+
+    }, {
+      key: '_hasTable',
+      value: function _hasTable() {
+        return 0 < this._tables.length;
+      }
+
+      /**
+       * @override
+       */
+
+    }, {
+      key: '_toParamString',
+      value: function _toParamString() {
+        var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+        var totalStr = '',
+            totalValues = [];
+
+        if (this._hasTable()) {
+          // retrieve the parameterised queries
+          var _iteratorNormalCompletion7 = true;
+          var _didIteratorError7 = false;
+          var _iteratorError7 = undefined;
+
+          try {
+            for (var _iterator7 = this._tables[Symbol.iterator](), _step7; !(_iteratorNormalCompletion7 = (_step7 = _iterator7.next()).done); _iteratorNormalCompletion7 = true) {
+              var _step7$value = _step7.value;
+              var table = _step7$value.table;
+              var alias = _step7$value.alias;
+
+              totalStr = _pad(totalStr, ', ');
+
+              var tableStr = void 0;
+
+              if (table instanceof cls.BaseBuilder) {
+                var _table$_toParamString = table._toParamString({
+                  buildParameterized: options.buildParameterized,
+                  nested: true
+                });
+
+                var text = _table$_toParamString.text;
+                var values = _table$_toParamString.values;
+
+
+                tableStr = text;
+                totalValues.push.apply(totalValues, _toConsumableArray(values));
+              } else {
+                tableStr = this._formatTableName(table);
+              }
+
+              if (alias) {
+                tableStr += ' ' + this._formatTableAlias(alias);
+              }
+
+              totalStr += tableStr;
+            }
+          } catch (err) {
+            _didIteratorError7 = true;
+            _iteratorError7 = err;
+          } finally {
+            try {
+              if (!_iteratorNormalCompletion7 && _iterator7.return) {
+                _iterator7.return();
+              }
+            } finally {
+              if (_didIteratorError7) {
+                throw _iteratorError7;
+              }
+            }
+          }
+
+          if (this.options.prefix) {
+            totalStr = this.options.prefix + ' ' + totalStr;
+          }
+        }
+
+        return {
+          text: totalStr,
+          values: totalValues
+        };
+      }
+    }]);
+
+    return _class8;
+  }(cls.Block);
+
+  // target table for DELETE queries, DELETE <??> FROM
+  cls.TargetTableBlock = function (_cls$AbstractTableBlo) {
+    _inherits(_class9, _cls$AbstractTableBlo);
+
+    function _class9() {
+      _classCallCheck(this, _class9);
+
+      return _possibleConstructorReturn(this, Object.getPrototypeOf(_class9).apply(this, arguments));
+    }
+
+    _createClass(_class9, [{
+      key: 'target',
+      value: function target(table) {
+        this._table(table);
+      }
+    }]);
+
+    return _class9;
+  }(cls.AbstractTableBlock);
+
+  // Update Table
+  cls.UpdateTableBlock = function (_cls$AbstractTableBlo2) {
+    _inherits(_class10, _cls$AbstractTableBlo2);
+
+    function _class10() {
+      _classCallCheck(this, _class10);
+
+      return _possibleConstructorReturn(this, Object.getPrototypeOf(_class10).apply(this, arguments));
+    }
+
+    _createClass(_class10, [{
+      key: 'table',
+      value: function table(_table2) {
+        var alias = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+
+        this._table(_table2, alias);
+      }
+    }, {
+      key: '_toParamString',
+      value: function _toParamString() {
+        var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+        if (!this._hasTable()) {
+          throw new Error("table() needs to be called");
+        }
+
+        return _get(Object.getPrototypeOf(_class10.prototype), '_toParamString', this).call(this, options);
+      }
+    }]);
+
+    return _class10;
+  }(cls.AbstractTableBlock);
+
+  // FROM table
+  cls.FromTableBlock = function (_cls$AbstractTableBlo3) {
+    _inherits(_class11, _cls$AbstractTableBlo3);
+
+    function _class11(options) {
+      _classCallCheck(this, _class11);
+
+      return _possibleConstructorReturn(this, Object.getPrototypeOf(_class11).call(this, _extend({}, options, {
+        prefix: 'FROM'
+      })));
+    }
+
+    _createClass(_class11, [{
+      key: 'from',
+      value: function from(table) {
+        var alias = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+
+        this._table(table, alias);
+      }
+    }]);
+
+    return _class11;
+  }(cls.AbstractTableBlock);
+
+  // INTO table
+  cls.IntoTableBlock = function (_cls$AbstractTableBlo4) {
+    _inherits(_class12, _cls$AbstractTableBlo4);
+
+    function _class12(options) {
+      _classCallCheck(this, _class12);
+
+      return _possibleConstructorReturn(this, Object.getPrototypeOf(_class12).call(this, _extend({}, options, {
+        prefix: 'INTO',
+        singleTable: true
+      })));
+    }
+
+    _createClass(_class12, [{
+      key: 'into',
+      value: function into(table) {
+        this._table(table);
+      }
+    }, {
+      key: '_toParamString',
+      value: function _toParamString() {
+        var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+        if (!this._hasTable()) {
+          throw new Error("into() needs to be called");
+        }
+
+        return _get(Object.getPrototypeOf(_class12.prototype), '_toParamString', this).call(this, options);
+      }
+    }]);
+
+    return _class12;
+  }(cls.AbstractTableBlock);
+
+  // (SELECT) Get field
+  cls.GetFieldBlock = function (_cls$Block4) {
+    _inherits(_class13, _cls$Block4);
+
+    function _class13(options) {
+      _classCallCheck(this, _class13);
+
+      var _this15 = _possibleConstructorReturn(this, Object.getPrototypeOf(_class13).call(this, options));
+
+      _this15._fields = [];
+      return _this15;
+    }
+
+    /**
+    # Add the given fields to the final result set.
+    #
+    # The parameter is an Object containing field names (or database functions) as the keys and aliases for the fields
+    # as the values. If the value for a key is null then no alias is set for that field.
+    #
+    # Internally this method simply calls the field() method of this block to add each individual field.
+    #
+    # options.ignorePeriodsForFieldNameQuotes - whether to ignore period (.) when automatically quoting the field name
+    */
+
+
+    _createClass(_class13, [{
+      key: 'fields',
+      value: function fields(_fields) {
+        var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
+
+        if (_isArray(_fields)) {
+          var _iteratorNormalCompletion8 = true;
+          var _didIteratorError8 = false;
+          var _iteratorError8 = undefined;
+
+          try {
+            for (var _iterator8 = _fields[Symbol.iterator](), _step8; !(_iteratorNormalCompletion8 = (_step8 = _iterator8.next()).done); _iteratorNormalCompletion8 = true) {
+              var field = _step8.value;
+
+              this.field(field, null, options);
+            }
+          } catch (err) {
+            _didIteratorError8 = true;
+            _iteratorError8 = err;
+          } finally {
+            try {
+              if (!_iteratorNormalCompletion8 && _iterator8.return) {
+                _iterator8.return();
+              }
+            } finally {
+              if (_didIteratorError8) {
+                throw _iteratorError8;
+              }
+            }
+          }
+        } else {
+          for (var _field2 in _fields) {
+            var alias = _fields[_field2];
+
+            this.field(_field2, alias, options);
+          }
+        }
+      }
+
+      /**
+      # Add the given field to the final result set.
+      #
+      # The 'field' parameter does not necessarily have to be a fieldname. It can use database functions too,
+      # e.g. DATE_FORMAT(a.started, "%H")
+      #
+      # An alias may also be specified for this field.
+      #
+      # options.ignorePeriodsForFieldNameQuotes - whether to ignore period (.) when automatically quoting the field name
+      */
+
+    }, {
+      key: 'field',
+      value: function field(_field) {
+        var alias = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+        var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2];
+
+        alias = alias ? this._sanitizeFieldAlias(alias) : alias;
+        _field = this._sanitizeField(_field);
+
+        // if field-alias combo already present then don't add
+        var existingField = this._fields.filter(function (f) {
+          return f.name === _field && f.alias === alias;
+        });
+        if (existingField.length) {
+          return this;
+        }
+
+        this._fields.push({
+          name: _field,
+          alias: alias,
+          options: options
+        });
+      }
+    }, {
+      key: '_toParamString',
+      value: function _toParamString() {
+        var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+        var queryBuilder = options.queryBuilder;
+        var buildParameterized = options.buildParameterized;
+
+
+        var totalStr = '',
+            totalValues = [];
+
+        var _iteratorNormalCompletion9 = true;
+        var _didIteratorError9 = false;
+        var _iteratorError9 = undefined;
+
+        try {
+          for (var _iterator9 = this._fields[Symbol.iterator](), _step9; !(_iteratorNormalCompletion9 = (_step9 = _iterator9.next()).done); _iteratorNormalCompletion9 = true) {
+            var field = _step9.value;
+
+            totalStr = _pad(totalStr, ", ");
+
+            var name = field.name;
+            var alias = field.alias;
+            var _options = field.options;
+
+
+            if (typeof name === 'string') {
+              totalStr += this._formatFieldName(name, _options);
+            } else {
+              var ret = name._toParamString({
+                nested: true,
+                buildParameterized: buildParameterized
+              });
+
+              totalStr += ret.text;
+              totalValues.push.apply(totalValues, _toConsumableArray(ret.values));
+            }
+
+            if (alias) {
+              totalStr += ' AS ' + this._formatFieldAlias(alias);
+            }
+          }
+        } catch (err) {
+          _didIteratorError9 = true;
+          _iteratorError9 = err;
+        } finally {
+          try {
+            if (!_iteratorNormalCompletion9 && _iterator9.return) {
+              _iterator9.return();
+            }
+          } finally {
+            if (_didIteratorError9) {
+              throw _iteratorError9;
+            }
+          }
+        }
+
+        if (!totalStr.length) {
+          // if select query and a table is set then all fields wanted
+          var fromTableBlock = queryBuilder && queryBuilder.getBlock(cls.FromTableBlock);
+          if (fromTableBlock && fromTableBlock._hasTable()) {
+            totalStr = "*";
+          }
+        }
+
+        return {
+          text: totalStr,
+          values: totalValues
+        };
+      }
+    }]);
+
+    return _class13;
+  }(cls.Block);
+
+  // Base class for setting fields to values (used for INSERT and UPDATE queries)
+  cls.AbstractSetFieldBlock = function (_cls$Block5) {
+    _inherits(_class14, _cls$Block5);
+
+    function _class14(options) {
+      _classCallCheck(this, _class14);
+
+      var _this16 = _possibleConstructorReturn(this, Object.getPrototypeOf(_class14).call(this, options));
+
+      _this16._reset();
+      return _this16;
+    }
+
+    _createClass(_class14, [{
+      key: '_reset',
+      value: function _reset() {
+        this._fields = [];
+        this._values = [[]];
+        this._valueOptions = [[]];
+      }
+
+      // Update the given field with the given value.
+      // This will override any previously set value for the given field.
+
+    }, {
+      key: '_set',
+      value: function _set(field, value) {
+        var valueOptions = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2];
+
+        if (this._values.length > 1) {
+          throw new Error("Cannot set multiple rows of fields this way.");
+        }
+
+        if (typeof value !== 'undefined') {
+          value = this._sanitizeValue(value);
+        }
+
+        field = this._sanitizeField(field);
+
+        // Explicity overwrite existing fields
+        var index = this._fields.indexOf(field);
+
+        // if field not defined before
+        if (-1 === index) {
+          this._fields.push(field);
+          index = this._fields.length - 1;
+        }
+
+        this._values[0][index] = value;
+        this._valueOptions[0][index] = valueOptions;
+      }
+
+      // Insert fields based on the key/value pairs in the given object
+
+    }, {
+      key: '_setFields',
+      value: function _setFields(fields) {
+        var valueOptions = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
+
+        if ((typeof fields === 'undefined' ? 'undefined' : _typeof(fields)) !== 'object') {
+          throw new Error("Expected an object but got " + (typeof fields === 'undefined' ? 'undefined' : _typeof(fields)));
+        }
+
+        for (var field in fields) {
+          this._set(field, fields[field], valueOptions);
+        }
+      }
+
+      // Insert multiple rows for the given fields. Accepts an array of objects.
+      // This will override all previously set values for every field.
+
+    }, {
+      key: '_setFieldsRows',
+      value: function _setFieldsRows(fieldsRows) {
+        var valueOptions = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
+
+        if (!_isArray(fieldsRows)) {
+          throw new Error("Expected an array of objects but got " + (typeof fieldsRows === 'undefined' ? 'undefined' : _typeof(fieldsRows)));
+        }
+
+        // Reset the objects stored fields and values
+        this._reset();
+
+        // for each row
+        for (var i = 0; fieldsRows.length > i; ++i) {
+          var fieldRow = fieldsRows[i];
+
+          // for each field
+          for (var field in fieldRow) {
+            var value = fieldRow[field];
+
+            field = this._sanitizeField(field);
+            value = this._sanitizeValue(value);
+
+            var index = this._fields.indexOf(field);
+
+            if (0 < i && -1 === index) {
+              throw new Error('All fields in subsequent rows must match the fields in the first row');
+            }
+
+            // Add field only if it hasn't been added before
+            if (-1 === index) {
+              this._fields.push(field);
+              index = this._fields.length - 1;
+            }
+
+            // The first value added needs to add the array
+            if (!_isArray(this._values[i])) {
+              this._values[i] = [];
+              this._valueOptions[i] = [];
+            }
+
+            this._values[i][index] = value;
+            this._valueOptions[i][index] = valueOptions;
+          }
+        }
+      }
+    }]);
+
+    return _class14;
+  }(cls.Block);
+
+  // (UPDATE) SET field=value
+  cls.SetFieldBlock = function (_cls$AbstractSetField) {
+    _inherits(_class15, _cls$AbstractSetField);
+
+    function _class15() {
+      _classCallCheck(this, _class15);
+
+      return _possibleConstructorReturn(this, Object.getPrototypeOf(_class15).apply(this, arguments));
+    }
+
+    _createClass(_class15, [{
+      key: 'set',
+      value: function set(field, value, options) {
+        this._set(field, value, options);
+      }
+    }, {
+      key: 'setFields',
+      value: function setFields(fields, valueOptions) {
+        this._setFields(fields, valueOptions);
+      }
+    }, {
+      key: '_toParamString',
+      value: function _toParamString() {
+        var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+        var buildParameterized = options.buildParameterized;
+
+
+        if (0 >= this._fields.length) {
+          throw new Error("set() needs to be called");
+        }
+
+        var totalStr = '',
+            totalValues = [];
+
+        for (var i = 0; i < this._fields.length; ++i) {
+          totalStr = _pad(totalStr, ', ');
+
+          var field = this._formatFieldName(this._fields[i]);
+          var value = this._values[0][i];
+
+          // e.g. field can be an expression such as `count = count + 1`
+          if (0 > field.indexOf('=')) {
+            field = field + ' = ' + this.options.parameterCharacter;
+          }
+
+          var ret = this._buildString(field, [value], {
+            buildParameterized: buildParameterized,
+            formattingOptions: this._valueOptions[0][i]
+          });
+
+          totalStr += ret.text;
+          totalValues.push.apply(totalValues, _toConsumableArray(ret.values));
+        }
+
+        return {
+          text: 'SET ' + totalStr,
+          values: totalValues
+        };
+      }
+    }]);
+
+    return _class15;
+  }(cls.AbstractSetFieldBlock);
+
+  // (INSERT INTO) ... field ... value
+  cls.InsertFieldValueBlock = function (_cls$AbstractSetField2) {
+    _inherits(_class16, _cls$AbstractSetField2);
+
+    function _class16() {
+      _classCallCheck(this, _class16);
+
+      return _possibleConstructorReturn(this, Object.getPrototypeOf(_class16).apply(this, arguments));
+    }
+
+    _createClass(_class16, [{
+      key: 'set',
+      value: function set(field, value) {
+        var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2];
+
+        this._set(field, value, options);
+      }
+    }, {
+      key: 'setFields',
+      value: function setFields(fields, valueOptions) {
+        this._setFields(fields, valueOptions);
+      }
+    }, {
+      key: 'setFieldsRows',
+      value: function setFieldsRows(fieldsRows, valueOptions) {
+        this._setFieldsRows(fieldsRows, valueOptions);
+      }
+    }, {
+      key: '_toParamString',
+      value: function _toParamString() {
+        var _this19 = this;
+
+        var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+        var buildParameterized = options.buildParameterized;
+
+
+        var fieldString = this._fields.map(function (f) {
+          return _this19._formatFieldName(f);
+        }).join(', ');
+
+        var valueStrings = [],
+            totalValues = [];
+
+        for (var i = 0; i < this._values.length; ++i) {
+          valueStrings[i] = '';
+
+          for (var j = 0; j < this._values[i].length; ++j) {
+            var ret = this._buildString(this.options.parameterCharacter, [this._values[i][j]], {
+              buildParameterized: buildParameterized,
+              formattingOptions: this._valueOptions[i][j]
+            });
+
+            totalValues.push.apply(totalValues, _toConsumableArray(ret.values));
+
+            valueStrings[i] = _pad(valueStrings[i], ', ');
+            valueStrings[i] += ret.text;
+          }
+        }
+
+        return {
+          text: fieldString.length ? '(' + fieldString + ') VALUES (' + valueStrings.join('), (') + ')' : '',
+          values: totalValues
+        };
+      }
+    }]);
+
+    return _class16;
+  }(cls.AbstractSetFieldBlock);
+
+  // (INSERT INTO) ... field ... (SELECT ... FROM ...)
+  cls.InsertFieldsFromQueryBlock = function (_cls$Block6) {
+    _inherits(_class17, _cls$Block6);
+
+    function _class17(options) {
+      _classCallCheck(this, _class17);
+
+      var _this20 = _possibleConstructorReturn(this, Object.getPrototypeOf(_class17).call(this, options));
+
+      _this20._fields = [];
+      _this20._query = null;
+      return _this20;
+    }
+
+    _createClass(_class17, [{
+      key: 'fromQuery',
+      value: function fromQuery(fields, selectQuery) {
+        var _this21 = this;
+
+        this._fields = fields.map(function (v) {
+          return _this21._sanitizeField(v);
+        });
+
+        this._query = this._sanitizeBaseBuilder(selectQuery);
+      }
+    }, {
+      key: '_toParamString',
+      value: function _toParamString() {
+        var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+        var totalStr = '',
+            totalValues = [];
+
+        if (this._fields.length && this._query) {
+          var _query$_toParamString = this._query._toParamString({
+            buildParameterized: options.buildParameterized,
+            nested: true
+          });
+
+          var text = _query$_toParamString.text;
+          var values = _query$_toParamString.values;
+
+
+          totalStr = '(' + this._fields.join(', ') + ') ' + this._applyNestingFormatting(text);
+          totalValues = values;
+        }
+
+        return {
+          text: totalStr,
+          values: totalValues
+        };
+      }
+    }]);
+
+    return _class17;
+  }(cls.Block);
+
+  // DISTINCT
+  cls.DistinctBlock = function (_cls$Block7) {
+    _inherits(_class18, _cls$Block7);
+
+    function _class18() {
+      _classCallCheck(this, _class18);
+
+      return _possibleConstructorReturn(this, Object.getPrototypeOf(_class18).apply(this, arguments));
+    }
+
+    _createClass(_class18, [{
+      key: 'distinct',
+
+      // Add the DISTINCT keyword to the query.
+      value: function distinct() {
+        this._useDistinct = true;
+      }
+    }, {
+      key: '_toParamString',
+      value: function _toParamString() {
+        return {
+          text: this._useDistinct ? "DISTINCT" : "",
+          values: []
+        };
+      }
+    }]);
+
+    return _class18;
+  }(cls.Block);
+
+  // GROUP BY
+  cls.GroupByBlock = function (_cls$Block8) {
+    _inherits(_class19, _cls$Block8);
+
+    function _class19(options) {
+      _classCallCheck(this, _class19);
+
+      var _this23 = _possibleConstructorReturn(this, Object.getPrototypeOf(_class19).call(this, options));
+
+      _this23._groups = [];
+      return _this23;
+    }
+
+    // Add a GROUP BY transformation for the given field.
+
+
+    _createClass(_class19, [{
+      key: 'group',
+      value: function group(field) {
+        this._groups.push(this._sanitizeField(field));
+      }
+    }, {
+      key: '_toParamString',
+      value: function _toParamString() {
+        var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+        return {
+          text: this._groups.length ? 'GROUP BY ' + this._groups.join(', ') : '',
+          values: []
+        };
+      }
+    }]);
+
+    return _class19;
+  }(cls.Block);
+
+  // OFFSET x
+  cls.OffsetBlock = function (_cls$Block9) {
+    _inherits(_class20, _cls$Block9);
+
+    function _class20(options) {
+      _classCallCheck(this, _class20);
+
+      var _this24 = _possibleConstructorReturn(this, Object.getPrototypeOf(_class20).call(this, options));
+
+      _this24._offsets = null;
+      return _this24;
+    }
+
+    /**
+    # Set the OFFSET transformation.
+    #
+    # Call this will override the previously set offset for this query. Also note that Passing 0 for 'max' will remove
+    # the offset.
+    */
+
+
+    _createClass(_class20, [{
+      key: 'offset',
+      value: function offset(start) {
+        this._offsets = this._sanitizeLimitOffset(start);
+      }
+    }, {
+      key: '_toParamString',
+      value: function _toParamString() {
+        return {
+          text: this._offsets ? 'OFFSET ' + this._offsets : '',
+          values: []
+        };
+      }
+    }]);
+
+    return _class20;
+  }(cls.Block);
+
+  // Abstract condition base class
+  cls.AbstractConditionBlock = function (_cls$Block10) {
+    _inherits(_class21, _cls$Block10);
+
+    /** 
+     * @param {String} options.verb The condition verb.
+     */
+
+    function _class21(options) {
+      _classCallCheck(this, _class21);
+
+      var _this25 = _possibleConstructorReturn(this, Object.getPrototypeOf(_class21).call(this, options));
+
+      _this25._conditions = [];
+      return _this25;
+    }
+
+    /**
+    # Add a condition.
+    #
+    # When the final query is constructed all the conditions are combined using the intersection (AND) operator.
+    #
+    # Concrete subclasses should provide a method which calls this
+    */
+
+
+    _createClass(_class21, [{
+      key: '_condition',
+      value: function _condition(condition) {
+        for (var _len7 = arguments.length, values = Array(_len7 > 1 ? _len7 - 1 : 0), _key7 = 1; _key7 < _len7; _key7++) {
+          values[_key7 - 1] = arguments[_key7];
+        }
+
+        condition = this._sanitizeExpression(condition);
+
+        this._conditions.push({
+          expr: condition,
+          values: values
+        });
+      }
+    }, {
+      key: '_toParamString',
+      value: function _toParamString() {
+        var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+        var totalStr = [],
+            totalValues = [];
+
+        var _iteratorNormalCompletion10 = true;
+        var _didIteratorError10 = false;
+        var _iteratorError10 = undefined;
+
+        try {
+          for (var _iterator10 = this._conditions[Symbol.iterator](), _step10; !(_iteratorNormalCompletion10 = (_step10 = _iterator10.next()).done); _iteratorNormalCompletion10 = true) {
+            var _step10$value = _step10.value;
+            var expr = _step10$value.expr;
+            var values = _step10$value.values;
+
+            var ret = expr instanceof cls.Expression ? expr._toParamString({
+              buildParameterized: options.buildParameterized
+            }) : this._buildString(expr, values, {
+              buildParameterized: options.buildParameterized
+            });
+
+            if (ret.text.length) {
+              totalStr.push(ret.text);
+            }
+
+            totalValues.push.apply(totalValues, _toConsumableArray(ret.values));
+          }
+        } catch (err) {
+          _didIteratorError10 = true;
+          _iteratorError10 = err;
+        } finally {
+          try {
+            if (!_iteratorNormalCompletion10 && _iterator10.return) {
+              _iterator10.return();
+            }
+          } finally {
+            if (_didIteratorError10) {
+              throw _iteratorError10;
+            }
+          }
+        }
+
+        if (totalStr.length) {
+          totalStr = totalStr.join(') AND (');
+        }
+
+        return {
+          text: totalStr.length ? this.options.verb + ' (' + totalStr + ')' : '',
+          values: totalValues
+        };
+      }
+    }]);
+
+    return _class21;
+  }(cls.Block);
+
+  // WHERE
+  cls.WhereBlock = function (_cls$AbstractConditio) {
+    _inherits(_class22, _cls$AbstractConditio);
+
+    function _class22(options) {
+      _classCallCheck(this, _class22);
+
+      return _possibleConstructorReturn(this, Object.getPrototypeOf(_class22).call(this, _extend({}, options, {
+        verb: 'WHERE'
+      })));
+    }
+
+    _createClass(_class22, [{
+      key: 'where',
+      value: function where(condition) {
+        for (var _len8 = arguments.length, values = Array(_len8 > 1 ? _len8 - 1 : 0), _key8 = 1; _key8 < _len8; _key8++) {
+          values[_key8 - 1] = arguments[_key8];
+        }
+
+        this._condition.apply(this, [condition].concat(values));
+      }
+    }]);
+
+    return _class22;
+  }(cls.AbstractConditionBlock);
+
+  // HAVING
+  cls.HavingBlock = function (_cls$AbstractConditio2) {
+    _inherits(_class23, _cls$AbstractConditio2);
+
+    function _class23(options) {
+      _classCallCheck(this, _class23);
+
+      return _possibleConstructorReturn(this, Object.getPrototypeOf(_class23).call(this, _extend({}, options, {
+        verb: 'HAVING'
+      })));
+    }
+
+    _createClass(_class23, [{
+      key: 'having',
+      value: function having(condition) {
+        for (var _len9 = arguments.length, values = Array(_len9 > 1 ? _len9 - 1 : 0), _key9 = 1; _key9 < _len9; _key9++) {
+          values[_key9 - 1] = arguments[_key9];
+        }
+
+        this._condition.apply(this, [condition].concat(values));
+      }
+    }]);
+
+    return _class23;
+  }(cls.AbstractConditionBlock);
+
+  // ORDER BY
+  cls.OrderByBlock = function (_cls$Block11) {
+    _inherits(_class24, _cls$Block11);
+
+    function _class24(options) {
+      _classCallCheck(this, _class24);
+
+      var _this28 = _possibleConstructorReturn(this, Object.getPrototypeOf(_class24).call(this, options));
+
+      _this28._orders = [];
+      return _this28;
+    }
+
+    /**
+    # Add an ORDER BY transformation for the given field in the given order.
+    #
+    # To specify descending order pass false for the 'asc' parameter.
+    */
+
+
+    _createClass(_class24, [{
+      key: 'order',
+      value: function order(field, asc) {
+        for (var _len10 = arguments.length, values = Array(_len10 > 2 ? _len10 - 2 : 0), _key10 = 2; _key10 < _len10; _key10++) {
+          values[_key10 - 2] = arguments[_key10];
+        }
+
+        field = this._sanitizeField(field);
+
+        asc = asc === undefined ? true : asc;
+        asc = asc !== null ? !!asc : asc;
+
+        this._orders.push({
+          field: field,
+          dir: asc,
+          values: values
+        });
+      }
+    }, {
+      key: '_toParamString',
+      value: function _toParamString() {
+        var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+        var totalStr = '',
+            totalValues = [];
+
+        var _iteratorNormalCompletion11 = true;
+        var _didIteratorError11 = false;
+        var _iteratorError11 = undefined;
+
+        try {
+          for (var _iterator11 = this._orders[Symbol.iterator](), _step11; !(_iteratorNormalCompletion11 = (_step11 = _iterator11.next()).done); _iteratorNormalCompletion11 = true) {
+            var _step11$value = _step11.value;
+            var field = _step11$value.field;
+            var dir = _step11$value.dir;
+            var values = _step11$value.values;
+
+            totalStr = _pad(totalStr, ', ');
+
+            var ret = this._buildString(field, values, {
+              buildParameterized: options.buildParameterized
+            });
+
+            totalStr += ret.text, totalValues.push.apply(totalValues, _toConsumableArray(ret.values));
+
+            if (dir !== null) {
+              totalStr += ' ' + (dir ? 'ASC' : 'DESC');
+            }
+          }
+        } catch (err) {
+          _didIteratorError11 = true;
+          _iteratorError11 = err;
+        } finally {
+          try {
+            if (!_iteratorNormalCompletion11 && _iterator11.return) {
+              _iterator11.return();
+            }
+          } finally {
+            if (_didIteratorError11) {
+              throw _iteratorError11;
+            }
+          }
+        }
+
+        return {
+          text: totalStr.length ? 'ORDER BY ' + totalStr : '',
+          values: totalValues
+        };
+      }
+    }]);
+
+    return _class24;
+  }(cls.Block);
+
+  // LIMIT
+  cls.LimitBlock = function (_cls$Block12) {
+    _inherits(_class25, _cls$Block12);
+
+    function _class25(options) {
+      _classCallCheck(this, _class25);
+
+      var _this29 = _possibleConstructorReturn(this, Object.getPrototypeOf(_class25).call(this, options));
+
+      _this29._limit = null;
+      return _this29;
+    }
+
+    /**
+    # Set the LIMIT transformation.
+    #
+    # Call this will override the previously set limit for this query. Also note that Passing 0 for 'max' will remove
+    # the limit.
+    */
+
+
+    _createClass(_class25, [{
+      key: 'limit',
+      value: function limit(_limit2) {
+        this._limit = this._sanitizeLimitOffset(_limit2);
+      }
+    }, {
+      key: '_toParamString',
+      value: function _toParamString() {
+        return {
+          text: null !== this._limit ? 'LIMIT ' + this._limit : '',
+          values: []
+        };
+      }
+    }]);
+
+    return _class25;
+  }(cls.Block);
+
+  // JOIN
+  cls.JoinBlock = function (_cls$Block13) {
+    _inherits(_class26, _cls$Block13);
+
+    function _class26(options) {
+      _classCallCheck(this, _class26);
+
+      var _this30 = _possibleConstructorReturn(this, Object.getPrototypeOf(_class26).call(this, options));
+
+      _this30._joins = [];
+      return _this30;
+    }
+
+    /**
+    # Add a JOIN with the given table.
+    #
+    # 'table' is the name of the table to join with.
+    #
+    # 'alias' is an optional alias for the table name.
+    #
+    # 'condition' is an optional condition (containing an SQL expression) for the JOIN.
+    #
+    # 'type' must be either one of INNER, OUTER, LEFT or RIGHT. Default is 'INNER'.
+    #
+    */
+
+
+    _createClass(_class26, [{
+      key: 'join',
+      value: function join(table) {
+        var alias = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+        var condition = arguments.length <= 2 || arguments[2] === undefined ? null : arguments[2];
+        var type = arguments.length <= 3 || arguments[3] === undefined ? 'INNER' : arguments[3];
+
+        table = this._sanitizeTable(table, true);
+        alias = alias ? this._sanitizeTableAlias(alias) : alias;
+        condition = condition ? this._sanitizeExpression(condition) : condition;
+
+        this._joins.push({
+          type: type,
+          table: table,
+          alias: alias,
+          condition: condition
+        });
+      }
+    }, {
+      key: 'left_join',
+      value: function left_join(table) {
+        var alias = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+        var condition = arguments.length <= 2 || arguments[2] === undefined ? null : arguments[2];
+
+        this.join(table, alias, condition, 'LEFT');
+      }
+    }, {
+      key: 'right_join',
+      value: function right_join(table) {
+        var alias = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+        var condition = arguments.length <= 2 || arguments[2] === undefined ? null : arguments[2];
+
+        this.join(table, alias, condition, 'RIGHT');
+      }
+    }, {
+      key: 'outer_join',
+      value: function outer_join(table) {
+        var alias = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+        var condition = arguments.length <= 2 || arguments[2] === undefined ? null : arguments[2];
+
+        this.join(table, alias, condition, 'OUTER');
+      }
+    }, {
+      key: 'left_outer_join',
+      value: function left_outer_join(table) {
+        var alias = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+        var condition = arguments.length <= 2 || arguments[2] === undefined ? null : arguments[2];
+
+        this.join(table, alias, condition, 'LEFT OUTER');
+      }
+    }, {
+      key: 'full_join',
+      value: function full_join(table) {
+        var alias = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+        var condition = arguments.length <= 2 || arguments[2] === undefined ? null : arguments[2];
+
+        this.join(table, alias, condition, 'FULL');
+      }
+    }, {
+      key: 'cross_join',
+      value: function cross_join(table) {
+        var alias = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+        var condition = arguments.length <= 2 || arguments[2] === undefined ? null : arguments[2];
+
+        this.join(table, alias, condition, 'CROSS');
+      }
+    }, {
+      key: '_toParamString',
+      value: function _toParamString() {
+        var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+        var totalStr = "",
+            totalValues = [];
+
+        var _iteratorNormalCompletion12 = true;
+        var _didIteratorError12 = false;
+        var _iteratorError12 = undefined;
+
+        try {
+          for (var _iterator12 = this._joins[Symbol.iterator](), _step12; !(_iteratorNormalCompletion12 = (_step12 = _iterator12.next()).done); _iteratorNormalCompletion12 = true) {
+            var _step12$value = _step12.value;
+            var type = _step12$value.type;
+            var table = _step12$value.table;
+            var alias = _step12$value.alias;
+            var condition = _step12$value.condition;
+
+            totalStr = _pad(totalStr, this.options.separator);
+
+            var tableStr = void 0;
+
+            if (table instanceof cls.BaseBuilder) {
+              var ret = table._toParamString({
+                buildParameterized: options.buildParameterized,
+                nested: true
+              });
+
+              totalValues.push.apply(totalValues, _toConsumableArray(ret.values));
+              tableStr = ret.text;
+            } else {
+              tableStr = this._formatTableName(table);
+            }
+
+            totalStr += type + ' JOIN ' + tableStr;
+
+            if (alias) {
+              totalStr += ' ' + this._formatTableAlias(alias);
+            }
+
+            if (condition) {
+              totalStr += ' ON ';
+
+              var _ret4 = void 0;
+
+              if (condition instanceof cls.Expression) {
+                _ret4 = condition._toParamString({
+                  buildParameterized: options.buildParameterized
+                });
+              } else {
+                _ret4 = this._buildString(condition, [], {
+                  buildParameterized: options.buildParameterized
+                });
+              }
+
+              totalStr += this._applyNestingFormatting(_ret4.text);
+              totalValues.push.apply(totalValues, _toConsumableArray(_ret4.values));
+            }
+          }
+        } catch (err) {
+          _didIteratorError12 = true;
+          _iteratorError12 = err;
+        } finally {
+          try {
+            if (!_iteratorNormalCompletion12 && _iterator12.return) {
+              _iterator12.return();
+            }
+          } finally {
+            if (_didIteratorError12) {
+              throw _iteratorError12;
+            }
+          }
+        }
+
+        return {
+          text: totalStr,
+          values: totalValues
+        };
+      }
+    }]);
+
+    return _class26;
+  }(cls.Block);
+
+  // UNION
+  cls.UnionBlock = function (_cls$Block14) {
+    _inherits(_class27, _cls$Block14);
+
+    function _class27(options) {
+      _classCallCheck(this, _class27);
+
+      var _this31 = _possibleConstructorReturn(this, Object.getPrototypeOf(_class27).call(this, options));
+
+      _this31._unions = [];
+      return _this31;
+    }
+
+    /**
+    # Add a UNION with the given table/query.
+    #
+    # 'table' is the name of the table or query to union with.
+    #
+    # 'type' must be either one of UNION or UNION ALL.... Default is 'UNION'.
+    */
+
+
+    _createClass(_class27, [{
+      key: 'union',
+      value: function union(table) {
+        var type = arguments.length <= 1 || arguments[1] === undefined ? 'UNION' : arguments[1];
+
+        table = this._sanitizeTable(table);
+
+        this._unions.push({
+          type: type,
+          table: table
+        });
+      }
+
+      // Add a UNION ALL with the given table/query.
+
+    }, {
+      key: 'union_all',
+      value: function union_all(table) {
+        this.union(table, 'UNION ALL');
+      }
+    }, {
+      key: '_toParamString',
+      value: function _toParamString() {
+        var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+        var totalStr = '',
+            totalValues = [];
+
+        var _iteratorNormalCompletion13 = true;
+        var _didIteratorError13 = false;
+        var _iteratorError13 = undefined;
+
+        try {
+          for (var _iterator13 = this._unions[Symbol.iterator](), _step13; !(_iteratorNormalCompletion13 = (_step13 = _iterator13.next()).done); _iteratorNormalCompletion13 = true) {
+            var _step13$value = _step13.value;
+            var type = _step13$value.type;
+            var table = _step13$value.table;
+
+            totalStr = _pad(totalStr, this.options.separator);
+
+            var tableStr = void 0;
+
+            if (table instanceof cls.BaseBuilder) {
+              var ret = table._toParamString({
+                buildParameterized: options.buildParameterized,
+                nested: true
+              });
+
+              tableStr = ret.text;
+              totalValues.push.apply(totalValues, _toConsumableArray(ret.values));
+            } else {
+              totalStr = this._formatTableName(table);
+            }
+
+            totalStr += type + ' ' + tableStr;
+          }
+        } catch (err) {
+          _didIteratorError13 = true;
+          _iteratorError13 = err;
+        } finally {
+          try {
+            if (!_iteratorNormalCompletion13 && _iterator13.return) {
+              _iterator13.return();
+            }
+          } finally {
+            if (_didIteratorError13) {
+              throw _iteratorError13;
+            }
+          }
+        }
+
+        return {
+          text: totalStr,
+          values: totalValues
+        };
+      }
+    }]);
+
+    return _class27;
+  }(cls.Block);
+
+  /*
+  # ---------------------------------------------------------------------------------------------------------
+  # ---------------------------------------------------------------------------------------------------------
+  # Query builders
+  # ---------------------------------------------------------------------------------------------------------
+  # ---------------------------------------------------------------------------------------------------------
+  */
+
+  /**
+  # Query builder base class
+  #
+  # Note that the query builder does not check the final query string for correctness.
+  #
+  # All the build methods in this object return the object instance for chained method calling purposes.
+  */
+  cls.QueryBuilder = function (_cls$BaseBuilder4) {
+    _inherits(_class28, _cls$BaseBuilder4);
+
+    /**
+    # Constructor
+    #
+    # blocks - array of cls.BaseBuilderBlock instances to build the query with.
+    */
+
+    function _class28(options, blocks) {
+      _classCallCheck(this, _class28);
+
+      var _this32 = _possibleConstructorReturn(this, Object.getPrototypeOf(_class28).call(this, options));
+
+      _this32.blocks = blocks || [];
+
+      // Copy exposed methods into myself
+      var _iteratorNormalCompletion14 = true;
+      var _didIteratorError14 = false;
+      var _iteratorError14 = undefined;
+
+      try {
+        for (var _iterator14 = _this32.blocks[Symbol.iterator](), _step14; !(_iteratorNormalCompletion14 = (_step14 = _iterator14.next()).done); _iteratorNormalCompletion14 = true) {
+          var block = _step14.value;
+
+          var exposedMethods = block.exposedMethods();
+
+          for (var methodName in exposedMethods) {
+            var methodBody = exposedMethods[methodName];
+
+            if (undefined !== _this32[methodName]) {
+              throw new Error('Builder already has a builder method called: ' + methodName);
+            }
+
+            (function (block, name, body) {
+              _this32[name] = function () {
+                for (var _len11 = arguments.length, args = Array(_len11), _key11 = 0; _key11 < _len11; _key11++) {
+                  args[_key11] = arguments[_key11];
+                }
+
+                body.call.apply(body, [block].concat(args));
+
+                return _this32;
+              };
+            })(block, methodName, methodBody);
+          }
+        }
+      } catch (err) {
+        _didIteratorError14 = true;
+        _iteratorError14 = err;
+      } finally {
+        try {
+          if (!_iteratorNormalCompletion14 && _iterator14.return) {
+            _iterator14.return();
+          }
+        } finally {
+          if (_didIteratorError14) {
+            throw _iteratorError14;
+          }
+        }
+      }
+
+      return _this32;
+    }
+
+    /**
+    # Register a custom value handler for this query builder and all its contained blocks.
+    #
+    # Note: This will override any globally registered handler for this value type.
+    */
+
+
+    _createClass(_class28, [{
+      key: 'registerValueHandler',
+      value: function registerValueHandler(type, handler) {
+        var _iteratorNormalCompletion15 = true;
+        var _didIteratorError15 = false;
+        var _iteratorError15 = undefined;
+
+        try {
+          for (var _iterator15 = this.blocks[Symbol.iterator](), _step15; !(_iteratorNormalCompletion15 = (_step15 = _iterator15.next()).done); _iteratorNormalCompletion15 = true) {
+            var block = _step15.value;
+
+            block.registerValueHandler(type, handler);
+          }
+        } catch (err) {
+          _didIteratorError15 = true;
+          _iteratorError15 = err;
+        } finally {
+          try {
+            if (!_iteratorNormalCompletion15 && _iterator15.return) {
+              _iterator15.return();
+            }
+          } finally {
+            if (_didIteratorError15) {
+              throw _iteratorError15;
+            }
+          }
+        }
+
+        _get(Object.getPrototypeOf(_class28.prototype), 'registerValueHandler', this).call(this, type, handler);
+
+        return this;
+      }
+
+      /**
+      # Update query builder options
+      #
+      # This will update the options for all blocks too. Use this method with caution as it allows you to change the
+      # behaviour of your query builder mid-build.
+      */
+
+    }, {
+      key: 'updateOptions',
+      value: function updateOptions(options) {
+        this.options = _extend({}, this.options, options);
+
+        var _iteratorNormalCompletion16 = true;
+        var _didIteratorError16 = false;
+        var _iteratorError16 = undefined;
+
+        try {
+          for (var _iterator16 = this.blocks[Symbol.iterator](), _step16; !(_iteratorNormalCompletion16 = (_step16 = _iterator16.next()).done); _iteratorNormalCompletion16 = true) {
+            var block = _step16.value;
+
+            block.options = _extend({}, block.options, options);
+          }
+        } catch (err) {
+          _didIteratorError16 = true;
+          _iteratorError16 = err;
+        } finally {
+          try {
+            if (!_iteratorNormalCompletion16 && _iterator16.return) {
+              _iterator16.return();
+            }
+          } finally {
+            if (_didIteratorError16) {
+              throw _iteratorError16;
+            }
+          }
+        }
+      }
+
+      // Get the final fully constructed query param obj.
+
+    }, {
+      key: '_toParamString',
+      value: function _toParamString() {
+        var _this33 = this,
+            _ref2;
+
+        var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+        options = _extend({}, this.options, options);
+
+        var blockResults = this.blocks.map(function (b) {
+          return b._toParamString({
+            buildParameterized: options.buildParameterized,
+            queryBuilder: _this33
+          });
+        });
+
+        var blockTexts = blockResults.map(function (b) {
+          return b.text;
+        });
+        var blockValues = blockResults.map(function (b) {
+          return b.values;
+        });
+
+        var totalStr = blockTexts.filter(function (v) {
+          return 0 < v.length;
+        }).join(options.separator);
+
+        var totalValues = (_ref2 = []).concat.apply(_ref2, _toConsumableArray(blockValues));
+
+        if (!options.nested) {
+          if (options.numberedParameters) {
+            (function () {
+              var i = undefined !== options.numberedParametersStartAt ? options.numberedParametersStartAt : 1;
+
+              // construct regex for searching
+              var regex = options.parameterCharacter.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
+
+              totalStr = totalStr.replace(new RegExp(regex, 'g'), function () {
+                return '' + options.numberedParametersPrefix + i++;
+              });
+            })();
+          }
+        }
+
+        return {
+          text: this._applyNestingFormatting(totalStr, !!options.nested),
+          values: totalValues
+        };
+      }
+
+      // Deep clone
+
+    }, {
+      key: 'clone',
+      value: function clone() {
+        var blockClones = this.blocks.map(function (v) {
+          return v.clone();
+        });
+
+        return new this.constructor(this.options, blockClones);
+      }
+
+      // Get a specific block
+
+    }, {
+      key: 'getBlock',
+      value: function getBlock(blockType) {
+        var filtered = this.blocks.filter(function (b) {
+          return b instanceof blockType;
+        });
+
+        return filtered[0];
+      }
+    }]);
+
+    return _class28;
+  }(cls.BaseBuilder);
+
+  // SELECT query builder.
+  cls.Select = function (_cls$QueryBuilder) {
+    _inherits(_class29, _cls$QueryBuilder);
+
+    function _class29(options) {
+      var blocks = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+
+      _classCallCheck(this, _class29);
+
+      blocks = blocks || [new cls.StringBlock(options, 'SELECT'), new cls.FunctionBlock(options), new cls.DistinctBlock(options), new cls.GetFieldBlock(options), new cls.FromTableBlock(options), new cls.JoinBlock(options), new cls.WhereBlock(options), new cls.GroupByBlock(options), new cls.HavingBlock(options), new cls.OrderByBlock(options), new cls.LimitBlock(options), new cls.OffsetBlock(options), new cls.UnionBlock(options)];
+
+      return _possibleConstructorReturn(this, Object.getPrototypeOf(_class29).call(this, options, blocks));
+    }
+
+    return _class29;
+  }(cls.QueryBuilder);
+
+  // UPDATE query builder.
+  cls.Update = function (_cls$QueryBuilder2) {
+    _inherits(_class30, _cls$QueryBuilder2);
+
+    function _class30(options) {
+      var blocks = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+
+      _classCallCheck(this, _class30);
+
+      blocks = blocks || [new cls.StringBlock(options, 'UPDATE'), new cls.UpdateTableBlock(options), new cls.SetFieldBlock(options), new cls.WhereBlock(options), new cls.OrderByBlock(options), new cls.LimitBlock(options)];
+
+      return _possibleConstructorReturn(this, Object.getPrototypeOf(_class30).call(this, options, blocks));
+    }
+
+    return _class30;
+  }(cls.QueryBuilder);
+
+  // DELETE query builder.
+  cls.Delete = function (_cls$QueryBuilder3) {
+    _inherits(_class31, _cls$QueryBuilder3);
+
+    function _class31(options) {
+      var blocks = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+
+      _classCallCheck(this, _class31);
+
+      blocks = blocks || [new cls.StringBlock(options, 'DELETE'), new cls.TargetTableBlock(options), new cls.FromTableBlock(_extend({}, options, {
+        singleTable: true
+      })), new cls.JoinBlock(options), new cls.WhereBlock(options), new cls.OrderByBlock(options), new cls.LimitBlock(options)];
+
+      return _possibleConstructorReturn(this, Object.getPrototypeOf(_class31).call(this, options, blocks));
+    }
+
+    return _class31;
+  }(cls.QueryBuilder);
+
+  // An INSERT query builder.
+  cls.Insert = function (_cls$QueryBuilder4) {
+    _inherits(_class32, _cls$QueryBuilder4);
+
+    function _class32(options) {
+      var blocks = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+
+      _classCallCheck(this, _class32);
+
+      blocks = blocks || [new cls.StringBlock(options, 'INSERT'), new cls.IntoTableBlock(options), new cls.InsertFieldValueBlock(options), new cls.InsertFieldsFromQueryBlock(options)];
+
+      return _possibleConstructorReturn(this, Object.getPrototypeOf(_class32).call(this, options, blocks));
+    }
+
+    return _class32;
+  }(cls.QueryBuilder);
+
+  var _squel = {
+    VERSION: '5.3.3',
+    flavour: flavour,
+    expr: function expr(options) {
+      return new cls.Expression(options);
+    },
+    case: function _case(name, options) {
+      return new cls.Case(name, options);
+    },
+    select: function select(options, blocks) {
+      return new cls.Select(options, blocks);
+    },
+    update: function update(options, blocks) {
+      return new cls.Update(options, blocks);
+    },
+    insert: function insert(options, blocks) {
+      return new cls.Insert(options, blocks);
+    },
+    delete: function _delete(options, blocks) {
+      return new cls.Delete(options, blocks);
+    },
+    str: function str() {
+      var inst = new cls.FunctionBlock();
+      inst.function.apply(inst, arguments);
+      return inst;
+    },
+    registerValueHandler: cls.registerValueHandler
+  };
+
+  // aliases
+  _squel.remove = _squel.delete;
+
+  // classes
+  _squel.cls = cls;
+
+  return _squel;
+}
+
+/**
+# ---------------------------------------------------------------------------------------------------------
+# ---------------------------------------------------------------------------------------------------------
+# Exported instance (and for use by flavour definitions further down).
+# ---------------------------------------------------------------------------------------------------------
+# ---------------------------------------------------------------------------------------------------------
+*/
+
+var squel = _buildSquel();
+
+/**
+# ---------------------------------------------------------------------------------------------------------
+# ---------------------------------------------------------------------------------------------------------
+# Squel SQL flavours
+# ---------------------------------------------------------------------------------------------------------
+# ---------------------------------------------------------------------------------------------------------
+*/
+
+// Available flavours
+squel.flavours = {};
+
+// Setup Squel for a particular SQL flavour
+squel.useFlavour = function () {
+  var flavour = arguments.length <= 0 || arguments[0] === undefined ? null : arguments[0];
+
+  if (!flavour) {
+    return squel;
+  }
+
+  if (squel.flavours[flavour] instanceof Function) {
+    var s = _buildSquel(flavour);
+
+    squel.flavours[flavour].call(null, s);
+
+    // add in flavour methods
+    s.flavours = squel.flavours;
+    s.useFlavour = squel.useFlavour;
+
+    return s;
+  } else {
+    throw new Error('Flavour not available: ' + flavour);
+  }
+};
+
+squel.flavours['mssql'] = function (_squel) {
+  var cls = _squel.cls;
+
+  cls.DefaultQueryBuilderOptions.replaceSingleQuotes = true;
+  cls.DefaultQueryBuilderOptions.autoQuoteAliasNames = false;
+  cls.DefaultQueryBuilderOptions.numberedParametersPrefix = '@';
+
+  _squel.registerValueHandler(Date, function (date) {
+    return '\'' + date.getUTCFullYear() + '-' + (date.getUTCMonth() + 1) + '-' + date.getUTCDate() + ' ' + date.getUTCHours() + ':' + date.getUTCMinutes() + ':' + date.getUTCSeconds() + '\'';
+  });
+
+  //�LIMIT,  OFFSET x and TOP x
+  cls.MssqlLimitOffsetTopBlock = function (_cls$Block15) {
+    _inherits(_class33, _cls$Block15);
+
+    function _class33(options) {
+      _classCallCheck(this, _class33);
+
+      var _this38 = _possibleConstructorReturn(this, Object.getPrototypeOf(_class33).call(this, options));
+
+      _this38._limits = null;
+      _this38._offsets = null;
+
+      // This is setup as one block to return many as they all have to use each others data at different times
+      // The build String of EITHER LIMIT OR TOP should execute, never both.
+
+      /**
+      # Set the LIMIT/TOP transformation.
+      #
+      # Call this will override the previously set limit for this query. Also note that Passing 0 for 'max' will remove
+      # the limit.
+      */
+      var _limit = function _limit(max) {
+        max = this._sanitizeLimitOffset(max);
+        this._parent._limits = max;
+      };
+
+      _this38.ParentBlock = function (_cls$Block16) {
+        _inherits(_class34, _cls$Block16);
+
+        function _class34(parent) {
+          _classCallCheck(this, _class34);
+
+          var _this39 = _possibleConstructorReturn(this, Object.getPrototypeOf(_class34).call(this, parent.options));
+
+          _this39._parent = parent;
+          return _this39;
+        }
+
+        return _class34;
+      }(cls.Block);
+
+      _this38.LimitBlock = function (_this38$ParentBlock) {
+        _inherits(_class35, _this38$ParentBlock);
+
+        function _class35(parent) {
+          _classCallCheck(this, _class35);
+
+          var _this40 = _possibleConstructorReturn(this, Object.getPrototypeOf(_class35).call(this, parent));
+
+          _this40.limit = _limit;
+          return _this40;
+        }
+
+        _createClass(_class35, [{
+          key: '_toParamString',
+          value: function _toParamString() {
+            var str = "";
+
+            if (this._parent._limits && this._parent._offsets) {
+              str = 'FETCH NEXT ' + this._parent._limits + ' ROWS ONLY';
+            }
+
+            return {
+              text: str,
+              values: []
+            };
+          }
+        }]);
+
+        return _class35;
+      }(_this38.ParentBlock);
+
+      _this38.TopBlock = function (_this38$ParentBlock2) {
+        _inherits(_class36, _this38$ParentBlock2);
+
+        function _class36(parent) {
+          _classCallCheck(this, _class36);
+
+          var _this41 = _possibleConstructorReturn(this, Object.getPrototypeOf(_class36).call(this, parent));
+
+          _this41.top = _limit;
+          return _this41;
+        }
+
+        _createClass(_class36, [{
+          key: '_toParamString',
+          value: function _toParamString() {
+            var str = "";
+
+            if (this._parent._limits && !this._parent._offsets) {
+              str = 'TOP (' + this._parent._limits + ')';
+            }
+
+            return {
+              text: str,
+              values: []
+            };
+          }
+        }]);
+
+        return _class36;
+      }(_this38.ParentBlock);
+
+      _this38.OffsetBlock = function (_this38$ParentBlock3) {
+        _inherits(_class37, _this38$ParentBlock3);
+
+        function _class37() {
+          _classCallCheck(this, _class37);
+
+          return _possibleConstructorReturn(this, Object.getPrototypeOf(_class37).apply(this, arguments));
+        }
+
+        _createClass(_class37, [{
+          key: 'offset',
+          value: function offset(start) {
+            this._parent._offsets = this._sanitizeLimitOffset(start);
+          }
+        }, {
+          key: '_toParamString',
+          value: function _toParamString() {
+            var str = "";
+
+            if (this._parent._offsets) {
+              str = 'OFFSET ' + this._parent._offsets + ' ROWS';
+            }
+
+            return {
+              text: str,
+              values: []
+            };
+          }
+        }]);
+
+        return _class37;
+      }(_this38.ParentBlock);
+      return _this38;
+    }
+
+    _createClass(_class33, [{
+      key: 'LIMIT',
+      value: function LIMIT() {
+        return new this.LimitBlock(this);
+      }
+    }, {
+      key: 'TOP',
+      value: function TOP() {
+        return new this.TopBlock(this);
+      }
+    }, {
+      key: 'OFFSET',
+      value: function OFFSET() {
+        return new this.OffsetBlock(this);
+      }
+    }]);
+
+    return _class33;
+  }(cls.Block);
+
+  cls.MssqlUpdateTopBlock = function (_cls$Block17) {
+    _inherits(_class38, _cls$Block17);
+
+    function _class38(options) {
+      _classCallCheck(this, _class38);
+
+      var _this43 = _possibleConstructorReturn(this, Object.getPrototypeOf(_class38).call(this, options));
+
+      _this43._limits = null;
+
+      _this43.limit = _this43.top = function (max) {
+        _this43._limits = _this43._sanitizeLimitOffset(max);
+      };
+      return _this43;
+    }
+
+    _createClass(_class38, [{
+      key: '_toParamString',
+      value: function _toParamString() {
+        return {
+          text: this._limits ? 'TOP (' + this._limits + ')' : "",
+          values: []
+        };
+      }
+    }]);
+
+    return _class38;
+  }(cls.Block);
+
+  cls.MssqlInsertFieldValueBlock = function (_cls$InsertFieldValue) {
+    _inherits(_class39, _cls$InsertFieldValue);
+
+    function _class39(options) {
+      _classCallCheck(this, _class39);
+
+      var _this44 = _possibleConstructorReturn(this, Object.getPrototypeOf(_class39).call(this, options));
+
+      _this44._outputs = [];
+      return _this44;
+    }
+
+    // add fields to the output clause
+
+
+    _createClass(_class39, [{
+      key: 'output',
+      value: function output(fields) {
+        var _this45 = this;
+
+        if ('string' === typeof fields) {
+          this._outputs.push('INSERTED.' + this._sanitizeField(fields));
+        } else {
+          fields.forEach(function (f) {
+            _this45._outputs.push('INSERTED.' + _this45._sanitizeField(f));
+          });
+        }
+      }
+    }, {
+      key: '_toParamString',
+      value: function _toParamString(options) {
+        var ret = _get(Object.getPrototypeOf(_class39.prototype), '_toParamString', this).call(this, options);
+
+        if (ret.text.length && 0 < this._outputs.length) {
+          var innerStr = 'OUTPUT ' + this._outputs.join(', ') + ' ';
+
+          var valuesPos = ret.text.indexOf('VALUES');
+
+          ret.text = ret.text.substr(0, valuesPos) + innerStr + ret.text.substr(valuesPos);
+        }
+
+        return ret;
+      }
+    }]);
+
+    return _class39;
+  }(cls.InsertFieldValueBlock);
+
+  cls.MssqlUpdateDeleteOutputBlock = function (_cls$Block18) {
+    _inherits(_class40, _cls$Block18);
+
+    function _class40(options) {
+      _classCallCheck(this, _class40);
+
+      var _this46 = _possibleConstructorReturn(this, Object.getPrototypeOf(_class40).call(this, options));
+
+      _this46._outputs = [];
+      return _this46;
+    }
+
+    /**
+    # Add the given fields to the final result set.
+    #
+    # The parameter is an Object containing field names (or database functions) as the keys and aliases for the fields
+    # as the values. If the value for a key is null then no alias is set for that field.
+    #
+    # Internally this method simply calls the field() method of this block to add each individual field.
+    */
+
+
+    _createClass(_class40, [{
+      key: 'outputs',
+      value: function outputs(_outputs) {
+        for (var output in _outputs) {
+          this.output(output, _outputs[output]);
+        }
+      }
+
+      /**
+      # Add the given field to the final result set.
+      #
+      # The 'field' parameter does not necessarily have to be a fieldname. It can use database functions too,
+      # e.g. DATE_FORMAT(a.started, "%H")
+      #
+      # An alias may also be specified for this field.
+      */
+
+    }, {
+      key: 'output',
+      value: function output(_output) {
+        var alias = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+
+        _output = this._sanitizeField(_output);
+        alias = alias ? this._sanitizeFieldAlias(alias) : alias;
+
+        this._outputs.push({
+          name: this.options.forDelete ? 'DELETED.' + _output : 'INSERTED.' + _output,
+          alias: alias
+        });
+      }
+    }, {
+      key: '_toParamString',
+      value: function _toParamString(queryBuilder) {
+        var totalStr = "";
+
+        if (this._outputs.length) {
+          var _iteratorNormalCompletion17 = true;
+          var _didIteratorError17 = false;
+          var _iteratorError17 = undefined;
+
+          try {
+            for (var _iterator17 = this._outputs[Symbol.iterator](), _step17; !(_iteratorNormalCompletion17 = (_step17 = _iterator17.next()).done); _iteratorNormalCompletion17 = true) {
+              var output = _step17.value;
+
+              totalStr = _pad(totalStr, ", ");
+
+              totalStr += output.name;
+
+              if (output.alias) {
+                totalStr += ' AS ' + this._formatFieldAlias(output.alias);
+              }
+            }
+          } catch (err) {
+            _didIteratorError17 = true;
+            _iteratorError17 = err;
+          } finally {
+            try {
+              if (!_iteratorNormalCompletion17 && _iterator17.return) {
+                _iterator17.return();
+              }
+            } finally {
+              if (_didIteratorError17) {
+                throw _iteratorError17;
+              }
+            }
+          }
+
+          totalStr = 'OUTPUT ' + totalStr;
+        }
+
+        return {
+          text: totalStr,
+          values: []
+        };
+      }
+    }]);
+
+    return _class40;
+  }(cls.Block);
+
+  // SELECT query builder.
+  cls.Select = function (_cls$QueryBuilder5) {
+    _inherits(_class41, _cls$QueryBuilder5);
+
+    function _class41(options) {
+      var blocks = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+
+      _classCallCheck(this, _class41);
+
+      var limitOffsetTopBlock = new cls.MssqlLimitOffsetTopBlock(options);
+
+      blocks = blocks || [new cls.StringBlock(options, 'SELECT'), new cls.DistinctBlock(options), limitOffsetTopBlock.TOP(), new cls.GetFieldBlock(options), new cls.FromTableBlock(options), new cls.JoinBlock(options), new cls.WhereBlock(options), new cls.GroupByBlock(options), new cls.OrderByBlock(options), limitOffsetTopBlock.OFFSET(), limitOffsetTopBlock.LIMIT(), new cls.UnionBlock(options)];
+
+      return _possibleConstructorReturn(this, Object.getPrototypeOf(_class41).call(this, options, blocks));
+    }
+
+    return _class41;
+  }(cls.QueryBuilder);
+
+  // Order By in update requires subquery
+
+  // UPDATE query builder.
+  cls.Update = function (_cls$QueryBuilder6) {
+    _inherits(_class42, _cls$QueryBuilder6);
+
+    function _class42(options) {
+      var blocks = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+
+      _classCallCheck(this, _class42);
+
+      blocks = blocks || [new cls.StringBlock(options, 'UPDATE'), new cls.MssqlUpdateTopBlock(options), new cls.UpdateTableBlock(options), new cls.SetFieldBlock(options), new cls.MssqlUpdateDeleteOutputBlock(options), new cls.WhereBlock(options)];
+
+      return _possibleConstructorReturn(this, Object.getPrototypeOf(_class42).call(this, options, blocks));
+    }
+
+    return _class42;
+  }(cls.QueryBuilder);
+
+  // Order By and Limit/Top in delete requires subquery
+
+  // DELETE query builder.
+  cls.Delete = function (_cls$QueryBuilder7) {
+    _inherits(_class43, _cls$QueryBuilder7);
+
+    function _class43(options) {
+      var blocks = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+
+      _classCallCheck(this, _class43);
+
+      blocks = blocks || [new cls.StringBlock(options, 'DELETE'), new cls.TargetTableBlock(options), new cls.FromTableBlock(_extend({}, options, { singleTable: true })), new cls.JoinBlock(options), new cls.MssqlUpdateDeleteOutputBlock(_extend({}, options, { forDelete: true })), new cls.WhereBlock(options), new cls.OrderByBlock(options), new cls.LimitBlock(options)];
+
+      return _possibleConstructorReturn(this, Object.getPrototypeOf(_class43).call(this, options, blocks));
+    }
+
+    return _class43;
+  }(cls.QueryBuilder);
+
+  // An INSERT query builder.
+  cls.Insert = function (_cls$QueryBuilder8) {
+    _inherits(_class44, _cls$QueryBuilder8);
+
+    function _class44(options) {
+      var blocks = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+
+      _classCallCheck(this, _class44);
+
+      blocks = blocks || [new cls.StringBlock(options, 'INSERT'), new cls.IntoTableBlock(options), new cls.MssqlInsertFieldValueBlock(options), new cls.InsertFieldsFromQueryBlock(options)];
+
+      return _possibleConstructorReturn(this, Object.getPrototypeOf(_class44).call(this, options, blocks));
+    }
+
+    return _class44;
+  }(cls.QueryBuilder);
+};
+
+// This file contains additional Squel commands for use with MySQL
+
+squel.flavours['mysql'] = function (_squel) {
+  var cls = _squel.cls;
+
+  // ON DUPLICATE KEY UPDATE ...
+  cls.MysqlOnDuplicateKeyUpdateBlock = function (_cls$AbstractSetField3) {
+    _inherits(_class45, _cls$AbstractSetField3);
+
+    function _class45() {
+      _classCallCheck(this, _class45);
+
+      return _possibleConstructorReturn(this, Object.getPrototypeOf(_class45).apply(this, arguments));
+    }
+
+    _createClass(_class45, [{
+      key: 'onDupUpdate',
+      value: function onDupUpdate(field, value, options) {
+        this._set(field, value, options);
+      }
+    }, {
+      key: '_toParamString',
+      value: function _toParamString() {
+        var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+        var totalStr = "",
+            totalValues = [];
+
+        for (var i = 0; i < this._fields.length; ++i) {
+          totalStr = _pad(totalStr, ', ');
+
+          var field = this._fields[i];
+
+          var value = this._values[0][i];
+
+          var valueOptions = this._valueOptions[0][i];
+
+          // e.g. if field is an expression such as: count = count + 1
+          if (typeof value === 'undefined') {
+            totalStr += field;
+          } else {
+            var ret = this._buildString(field + ' = ' + this.options.parameterCharacter, [value], {
+              buildParameterized: options.buildParameterized,
+              formattingOptions: valueOptions
+            });
+
+            totalStr += ret.text;
+            totalValues.push.apply(totalValues, _toConsumableArray(ret.values));
+          }
+        }
+
+        return {
+          text: !totalStr.length ? "" : 'ON DUPLICATE KEY UPDATE ' + totalStr,
+          values: totalValues
+        };
+      }
+    }]);
+
+    return _class45;
+  }(cls.AbstractSetFieldBlock);
+
+  // INSERT query builder.
+  cls.Insert = function (_cls$QueryBuilder9) {
+    _inherits(_class46, _cls$QueryBuilder9);
+
+    function _class46(options) {
+      var blocks = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+
+      _classCallCheck(this, _class46);
+
+      blocks = blocks || [new cls.StringBlock(options, 'INSERT'), new cls.IntoTableBlock(options), new cls.InsertFieldValueBlock(options), new cls.InsertFieldsFromQueryBlock(options), new cls.MysqlOnDuplicateKeyUpdateBlock(options)];
+
+      return _possibleConstructorReturn(this, Object.getPrototypeOf(_class46).call(this, options, blocks));
+    }
+
+    return _class46;
+  }(cls.QueryBuilder);
+};
+
+// This file contains additional Squel commands for use with the Postgres DB engine
+squel.flavours['postgres'] = function (_squel) {
+  var cls = _squel.cls;
+
+  cls.DefaultQueryBuilderOptions.numberedParameters = true;
+  cls.DefaultQueryBuilderOptions.numberedParametersStartAt = 1;
+  cls.DefaultQueryBuilderOptions.autoQuoteAliasNames = false;
+  cls.DefaultQueryBuilderOptions.useAsForTableAliasNames = true;
+
+  // RETURNING
+  cls.ReturningBlock = function (_cls$Block19) {
+    _inherits(_class47, _cls$Block19);
+
+    function _class47(options) {
+      _classCallCheck(this, _class47);
+
+      var _this53 = _possibleConstructorReturn(this, Object.getPrototypeOf(_class47).call(this, options));
+
+      _this53._str = null;
+      return _this53;
+    }
+
+    _createClass(_class47, [{
+      key: 'returning',
+      value: function returning(ret) {
+        this._str = this._sanitizeField(ret);
+      }
+    }, {
+      key: '_toParamString',
+      value: function _toParamString() {
+        return {
+          text: this._str ? 'RETURNING ' + this._str : '',
+          values: []
+        };
+      }
+    }]);
+
+    return _class47;
+  }(cls.Block);
+
+  // WITH
+  cls.WithBlock = function (_cls$Block20) {
+    _inherits(_class48, _cls$Block20);
+
+    function _class48(options) {
+      _classCallCheck(this, _class48);
+
+      var _this54 = _possibleConstructorReturn(this, Object.getPrototypeOf(_class48).call(this, options));
+
+      _this54._tables = [];
+      return _this54;
+    }
+
+    _createClass(_class48, [{
+      key: 'with',
+      value: function _with(alias, table) {
+        this._tables.push({ alias: alias, table: table });
+      }
+    }, {
+      key: '_toParamString',
+      value: function _toParamString() {
+        var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+        var parts = [];
+        var values = [];
+
+        var _iteratorNormalCompletion18 = true;
+        var _didIteratorError18 = false;
+        var _iteratorError18 = undefined;
+
+        try {
+          for (var _iterator18 = this._tables[Symbol.iterator](), _step18; !(_iteratorNormalCompletion18 = (_step18 = _iterator18.next()).done); _iteratorNormalCompletion18 = true) {
+            var _step18$value = _step18.value;
+            var alias = _step18$value.alias;
+            var table = _step18$value.table;
+
+            var ret = table._toParamString({
+              buildParameterized: options.buildParameterized,
+              nested: true
+            });
+
+            parts.push(alias + ' AS ' + ret.text);
+            values.push.apply(values, _toConsumableArray(ret.values));
+          }
+        } catch (err) {
+          _didIteratorError18 = true;
+          _iteratorError18 = err;
+        } finally {
+          try {
+            if (!_iteratorNormalCompletion18 && _iterator18.return) {
+              _iterator18.return();
+            }
+          } finally {
+            if (_didIteratorError18) {
+              throw _iteratorError18;
+            }
+          }
+        }
+
+        return {
+          text: parts.length ? 'WITH ' + parts.join(', ') : '',
+          values: values
+        };
+      }
+    }]);
+
+    return _class48;
+  }(cls.Block);
+
+  // DISTINCT [ON]
+  cls.DistinctOnBlock = function (_cls$Block21) {
+    _inherits(_class49, _cls$Block21);
+
+    function _class49(options) {
+      _classCallCheck(this, _class49);
+
+      var _this55 = _possibleConstructorReturn(this, Object.getPrototypeOf(_class49).call(this, options));
+
+      _this55._distinctFields = [];
+      return _this55;
+    }
+
+    _createClass(_class49, [{
+      key: 'distinct',
+      value: function distinct() {
+        var _this56 = this;
+
+        this._useDistinct = true;
+
+        // Add all fields to the DISTINCT ON clause.
+
+        for (var _len12 = arguments.length, fields = Array(_len12), _key12 = 0; _key12 < _len12; _key12++) {
+          fields[_key12] = arguments[_key12];
+        }
+
+        fields.forEach(function (field) {
+          _this56._distinctFields.push(_this56._sanitizeField(field));
+        });
+      }
+    }, {
+      key: '_toParamString',
+      value: function _toParamString() {
+        var text = '';
+
+        if (this._useDistinct) {
+          text = 'DISTINCT';
+
+          if (this._distinctFields.length) {
+            text += ' ON (' + this._distinctFields.join(', ') + ')';
+          }
+        }
+
+        return {
+          text: text,
+          values: []
+        };
+      }
+    }]);
+
+    return _class49;
+  }(cls.Block);
+
+  // SELECT query builder.
+  cls.Select = function (_cls$QueryBuilder10) {
+    _inherits(_class50, _cls$QueryBuilder10);
+
+    function _class50(options) {
+      var blocks = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+
+      _classCallCheck(this, _class50);
+
+      blocks = blocks || [new cls.WithBlock(options), new cls.StringBlock(options, 'SELECT'), new cls.FunctionBlock(options), new cls.DistinctOnBlock(options), new cls.GetFieldBlock(options), new cls.FromTableBlock(options), new cls.JoinBlock(options), new cls.WhereBlock(options), new cls.GroupByBlock(options), new cls.HavingBlock(options), new cls.OrderByBlock(options), new cls.LimitBlock(options), new cls.OffsetBlock(options), new cls.UnionBlock(options)];
+
+      return _possibleConstructorReturn(this, Object.getPrototypeOf(_class50).call(this, options, blocks));
+    }
+
+    return _class50;
+  }(cls.QueryBuilder);
+
+  // INSERT query builder
+  cls.Insert = function (_cls$QueryBuilder11) {
+    _inherits(_class51, _cls$QueryBuilder11);
+
+    function _class51(options) {
+      var blocks = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+
+      _classCallCheck(this, _class51);
+
+      blocks = blocks || [new cls.WithBlock(options), new cls.StringBlock(options, 'INSERT'), new cls.IntoTableBlock(options), new cls.InsertFieldValueBlock(options), new cls.InsertFieldsFromQueryBlock(options), new cls.ReturningBlock(options)];
+
+      return _possibleConstructorReturn(this, Object.getPrototypeOf(_class51).call(this, options, blocks));
+    }
+
+    return _class51;
+  }(cls.QueryBuilder);
+
+  // UPDATE query builder
+  cls.Update = function (_cls$QueryBuilder12) {
+    _inherits(_class52, _cls$QueryBuilder12);
+
+    function _class52(options) {
+      var blocks = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+
+      _classCallCheck(this, _class52);
+
+      blocks = blocks || [new cls.WithBlock(options), new cls.StringBlock(options, 'UPDATE'), new cls.UpdateTableBlock(options), new cls.SetFieldBlock(options), new cls.FromTableBlock(options), new cls.WhereBlock(options), new cls.OrderByBlock(options), new cls.LimitBlock(options), new cls.ReturningBlock(options)];
+
+      return _possibleConstructorReturn(this, Object.getPrototypeOf(_class52).call(this, options, blocks));
+    }
+
+    return _class52;
+  }(cls.QueryBuilder);
+
+  // DELETE query builder
+  cls.Delete = function (_cls$QueryBuilder13) {
+    _inherits(_class53, _cls$QueryBuilder13);
+
+    function _class53(options) {
+      var blocks = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+
+      _classCallCheck(this, _class53);
+
+      blocks = blocks || [new cls.WithBlock(options), new cls.StringBlock(options, 'DELETE'), new cls.TargetTableBlock(options), new cls.FromTableBlock(_extend({}, options, {
+        singleTable: true
+      })), new cls.JoinBlock(options), new cls.WhereBlock(options), new cls.OrderByBlock(options), new cls.LimitBlock(options), new cls.ReturningBlock(options)];
+
+      return _possibleConstructorReturn(this, Object.getPrototypeOf(_class53).call(this, options, blocks));
+    }
+
+    return _class53;
+  }(cls.QueryBuilder);
+};
+return squel;
+}));

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
www/lib/squel/dist/squel.min.js


+ 108 - 0
www/lib/squel/gulpfile.js

@@ -0,0 +1,108 @@
+require('coffee-script/register');
+
+const gulp = require('gulp'),
+  umd = require('gulp-umd'),
+  path = require('path'),
+  concat = require('gulp-concat'),
+  insert = require('gulp-insert'),
+  mocha = require('gulp-mocha'),
+  babel = require('gulp-babel'),
+  replace = require('gulp-replace'),
+  uglify = require('gulp-uglify'),
+  runSequence = require('run-sequence'),
+  argv = require('yargs').argv;
+
+
+const onlyTest = argv.onlyTest || argv.limitTest;
+
+
+const SQUEL_VERSION = require('./package.json').version;
+
+
+gulp.task('build-basic', function() {
+
+  return gulp.src([
+      './src/core.js',
+    ])
+    .pipe( concat('squel-basic.js') )
+    .pipe( replace(/<<VERSION_STRING>>/i, SQUEL_VERSION) )
+    .pipe( babel({
+      presets: ['es2015']
+    }) )
+    .pipe( umd({
+      exports: function (file) {
+        return 'squel';
+      },
+      namespace: function(file) {
+        return 'squel';
+      }
+    }))
+    .pipe( gulp.dest('./dist') )
+    .pipe( uglify() )
+    .pipe( insert.prepend('/*! squel | https://github.com/hiddentao/squel | BSD license */') )
+    .pipe( concat('squel-basic.min.js') )
+    .pipe( gulp.dest('./dist') )
+});
+
+
+gulp.task('build-full', function() {
+  return gulp.src([
+      './src/core.js',
+      './src/mssql.js',
+      './src/mysql.js',
+      './src/postgres.js',
+    ])
+    .pipe( concat('squel.js') )
+    .pipe( replace(/<<VERSION_STRING>>/i, SQUEL_VERSION) )
+    .pipe( babel({
+      presets: ['es2015']
+    }) )
+    .pipe( umd({
+      exports: function (file) {
+        return 'squel';
+      },
+      namespace: function(file) {
+        return 'squel';
+      }
+    }))
+    .pipe( gulp.dest('./dist') )
+    .pipe( uglify() )
+    .pipe( insert.prepend('/*! squel | https://github.com/hiddentao/squel | BSD license */') )
+    .pipe( concat('squel.min.js') )
+    .pipe( gulp.dest('./dist') )
+});
+
+
+gulp.task('build', ['build-basic', 'build-full']);
+
+
+gulp.task('test', function () {
+  return gulp.src(onlyTest || [
+      './test/baseclasses.test.coffee',
+      './test/blocks.test.coffee',
+      './test/case.test.coffee',
+      './test/custom.test.coffee',
+      './test/delete.test.coffee',
+      './test/expressions.test.coffee',
+      './test/insert.test.coffee',
+      './test/select.test.coffee',
+      './test/update.test.coffee',
+      './test/mssql.test.coffee',
+      './test/mysql.test.coffee',
+      './test/postgres.test.coffee',
+    ], { read: false })
+      .pipe(mocha({
+        ui: 'exports',
+        reporter: 'spec',
+      }))
+    ;
+});
+
+
+
+gulp.task('default', function(cb) {
+  runSequence(['build'], 'test', cb);
+});
+
+
+

+ 50 - 0
www/lib/squel/package.json

@@ -0,0 +1,50 @@
+{
+  "name": "squel",
+  "description": "SQL query string builder",
+  "version": "5.3.3",
+  "author": "Ramesh Nair <ram@hiddentao.com> (http://www.hiddentao.com/)",
+  "contributors": [
+    "Ramesh Nair <ram@hiddentao.com> (http://www.hiddentao.com/)",
+    "Sergej Brjuchanov <serges@seznam.cz>"
+  ],
+  "dependencies": {},
+  "devDependencies": {
+    "babel-preset-es2015": "^6.6.0",
+    "chai": "1.5.x",
+    "coffee-script": "^1.10.0",
+    "gulp": "^3.9.1",
+    "gulp-babel": "^6.1.2",
+    "gulp-concat": "^2.6.0",
+    "gulp-insert": "^0.5.0",
+    "gulp-mocha": "^2.2.0",
+    "gulp-replace": "^0.5.4",
+    "gulp-uglify": "^1.5.3",
+    "gulp-umd": "^0.2.0",
+    "load-grunt-tasks": "~0.1.0",
+    "mocha": "1.9.x",
+    "run-sequence": "^1.1.5",
+    "sinon": "1.6.x",
+    "time-grunt": "~0.1.1",
+    "uglify-js": "1.3.x",
+    "underscore": "1.4.x",
+    "yargs": "^4.7.1"
+  },
+  "keywords": [
+    "sql",
+    "database",
+    "rdbms"
+  ],
+  "main": "dist/squel.js",
+  "scripts": {
+    "test": "gulp",
+    "build": "gulp",
+    "prepublish": "npm run build"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/hiddentao/squel.git"
+  },
+  "engines": {
+    "node": ">= 0.12.0"
+  }
+}

+ 2180 - 0
www/lib/squel/src/core.js

@@ -0,0 +1,2180 @@
+// append to string if non-empty
+function _pad (str, pad) {
+  return (str.length) ? str + pad : str;
+}
+
+
+// Extend given object's with other objects' properties, overriding existing ones if necessary
+function _extend (dst, ...sources) {
+  if (dst && sources) {
+    for (let src of sources) {
+      if (typeof src === 'object') {
+        Object.getOwnPropertyNames(src).forEach(function (key) {
+          dst[key] = src[key];
+        });
+      }
+    }
+  }
+
+  return dst;
+};
+
+
+
+// get whether object is a plain object
+function _isPlainObject(obj) {
+  return (obj && obj.constructor.prototype === Object.prototype);
+};
+
+
+// get whether object is an array
+function _isArray(obj) {
+  return (obj && obj.constructor.prototype === Array.prototype);
+};
+
+
+// get class name of given object
+function _getObjectClassName (obj) {
+  if (obj && obj.constructor && obj.constructor.toString) {
+    let arr = obj.constructor.toString().match(/function\s*(\w+)/);
+    
+    if (arr && 2 === arr.length) {
+      return arr[1]
+    }
+  }
+}
+
+
+// clone given item
+function _clone(src) {
+  if (!src) {
+    return src;
+  }
+
+  if (typeof src.clone === 'function') {
+    return src.clone();
+  } else if (_isPlainObject(src) || _isArray(src)) {
+    let ret = new (src.constructor);
+
+    Object.getOwnPropertyNames(src).forEach(function(key) {
+      if (typeof src[key] !== 'function') {
+        ret[key] = _clone(src[key]);
+      }
+    });
+
+    return ret;
+  } else {
+    return JSON.parse(JSON.stringify(src));
+  }
+};
+
+
+/**
+ * Register a value type handler
+ *
+ * Note: this will override any existing handler registered for this value type.
+ */
+function registerValueHandler (handlers, type, handler) {
+  let typeofType = typeof type;
+
+  if (typeofType !== 'function' && typeofType !== 'string') {
+    throw new Error("type must be a class constructor or string");
+  }
+
+  if (typeof handler !== 'function') {
+    throw new Error("handler must be a function");
+  }
+
+  for (let typeHandler of handlers) {
+    if (typeHandler.type === type) {
+      typeHandler.handler = handler;
+
+      return;
+    }
+  }
+
+  handlers.push({
+    type: type,
+    handler: handler,
+  });
+};
+
+
+
+
+/**
+ * Get value type handler for given type
+ */
+function getValueHandler (value, ...handlerLists) {
+  for (let handlers of handlerLists) {
+    for (let typeHandler of handlers) {
+      // if type is a string then use `typeof` or else use `instanceof`
+      if (typeof value === typeHandler.type || 
+          (typeof typeHandler.type !== 'string' && value instanceof typeHandler.type) ) {
+        return typeHandler.handler;
+      }
+    }
+  }
+};
+
+
+/**
+ * Build base squel classes and methods
+ */
+function _buildSquel(flavour = null) {
+  let cls = {
+    _getObjectClassName: _getObjectClassName,
+  };
+
+  // default query builder options
+  cls.DefaultQueryBuilderOptions = {
+    // If true then table names will be rendered inside quotes. The quote character used is configurable via the nameQuoteCharacter option.
+    autoQuoteTableNames: false,
+    // If true then field names will rendered inside quotes. The quote character used is configurable via the nameQuoteCharacter option.
+    autoQuoteFieldNames: false,
+    // If true then alias names will rendered inside quotes. The quote character used is configurable via the `tableAliasQuoteCharacter` and `fieldAliasQuoteCharacter` options.
+    autoQuoteAliasNames: true,
+    // If true then table alias names will rendered after AS keyword.
+    useAsForTableAliasNames: false,
+    // The quote character used for when quoting table and field names
+    nameQuoteCharacter: '`',
+    // The quote character used for when quoting table alias names
+    tableAliasQuoteCharacter: '`',
+    // The quote character used for when quoting table alias names
+    fieldAliasQuoteCharacter: '"',
+    // Custom value handlers where key is the value type and the value is the handler function
+    valueHandlers: [],
+    // Character used to represent a parameter value
+    parameterCharacter: '?',
+    // Numbered parameters returned from toParam() as $1, $2, etc.
+    numberedParameters: false,
+    // Numbered parameters prefix character(s)
+    numberedParametersPrefix: '$',
+    // Numbered parameters start at this number.
+    numberedParametersStartAt: 1,
+    // If true then replaces all single quotes within strings. The replacement string used is configurable via the `singleQuoteReplacement` option.
+    replaceSingleQuotes: false,
+    // The string to replace single quotes with in query strings
+    singleQuoteReplacement: '\'\'',
+    // String used to join individual blocks in a query when it's stringified
+    separator: ' ',
+    // Function for formatting string values prior to insertion into query string
+    stringFormatter: null,
+  };
+
+  // Global custom value handlers for all instances of builder
+  cls.globalValueHandlers = [];
+
+
+  /*
+  # ---------------------------------------------------------------------------------------------------------
+  # ---------------------------------------------------------------------------------------------------------
+  # Custom value types
+  # ---------------------------------------------------------------------------------------------------------
+  # ---------------------------------------------------------------------------------------------------------
+   */
+
+
+  // Register a new value handler
+  cls.registerValueHandler = function(type, handler) {
+    registerValueHandler(cls.globalValueHandlers, type, handler);
+  };
+
+
+  /*
+  # ---------------------------------------------------------------------------------------------------------
+  # ---------------------------------------------------------------------------------------------------------
+  # Base classes
+  # ---------------------------------------------------------------------------------------------------------
+  # ---------------------------------------------------------------------------------------------------------
+  */
+
+  // Base class for cloneable builders
+  cls.Cloneable = class {
+    /**
+     * Clone this builder
+     */
+    clone () {
+      let newInstance = new this.constructor;
+
+      return _extend(newInstance, _clone(_extend({}, this)));
+    }
+  }
+
+
+
+  // Base class for all builders
+  cls.BaseBuilder = class extends cls.Cloneable {
+    /**
+     * Constructor.
+     * this.param  {Object} options Overriding one or more of `cls.DefaultQueryBuilderOptions`.
+     */
+    constructor (options) {
+      super();
+
+      let defaults = JSON.parse(JSON.stringify(cls.DefaultQueryBuilderOptions));
+
+      this.options = _extend({}, defaults, options);
+    }
+
+    /**
+     * Register a custom value handler for this builder instance.
+     *
+     * Note: this will override any globally registered handler for this value type.
+     */
+    registerValueHandler (type, handler) {
+      registerValueHandler(this.options.valueHandlers, type, handler);
+
+      return this;
+    }
+
+
+    /**
+     * Sanitize given expression.
+     */
+    _sanitizeExpression (expr) {
+      // If it's not an Expression builder instance
+      if (!(expr instanceof cls.Expression)) {
+        // It must then be a string
+        if (typeof expr !== "string") {
+          throw new Error("expression must be a string or Expression instance");
+        }
+      }
+
+      return expr;
+    }
+
+
+
+    /**
+     * Sanitize the given name.
+     *
+     * The 'type' parameter is used to construct a meaningful error message in case validation fails.
+     */
+    _sanitizeName (value, type) {
+      if (typeof value !== "string") {
+        throw new Error(`${type} must be a string`);
+      }
+
+      return value;
+    }
+
+
+    _sanitizeField (item) {
+      if (!(item instanceof cls.BaseBuilder)) {
+        item = this._sanitizeName(item, "field name");
+      }
+
+      return item;
+    }
+
+
+    _sanitizeBaseBuilder (item) {
+      if (item instanceof cls.BaseBuilder) {
+        return item;
+      }
+
+      throw new Error("must be a BaseBuilder instance");
+    }
+
+
+    _sanitizeTable (item) {
+      if (typeof item !== "string") {
+        try {
+          item = this._sanitizeBaseBuilder(item);
+        } catch (e) {
+          throw new Error("table name must be a string or a query builder");
+        }
+      } else {
+        item = this._sanitizeName(item, 'table');
+      }
+
+      return item;
+    }
+
+    _sanitizeTableAlias (item) {
+      return this._sanitizeName(item, "table alias");
+    }
+
+
+    _sanitizeFieldAlias (item) {
+      return this._sanitizeName(item, "field alias");
+    }
+
+
+    // Sanitize the given limit/offset value.
+    _sanitizeLimitOffset (value) {
+      value = parseInt(value);
+
+      if (0 > value || isNaN(value)) {
+        throw new Error("limit/offset must be >= 0");
+      }
+
+      return value;
+    }
+
+
+    // Santize the given field value
+    _sanitizeValue (item) {
+      let itemType = typeof item;
+
+      if (null === item) {
+        // null is allowed
+      }
+      else if ("string" === itemType || "number" === itemType || "boolean" === itemType) {
+        // primitives are allowed
+      }
+      else if (item instanceof cls.BaseBuilder) {
+        // Builders allowed
+      }
+      else {
+        let typeIsValid = 
+          !!getValueHandler(item, this.options.valueHandlers, cls.globalValueHandlers);
+
+        if (!typeIsValid) {
+          throw new Error("field value must be a string, number, boolean, null or one of the registered custom value types");
+        }
+      }
+
+      return item;
+    }
+
+
+    // Escape a string value, e.g. escape quotes and other characters within it.
+    _escapeValue (value) {
+      return (!this.options.replaceSingleQuotes) ? value : (
+        value.replace(/\'/g, this.options.singleQuoteReplacement)
+      );
+    }
+
+
+    _formatTableName (item) {
+      if (this.options.autoQuoteTableNames) {
+        const quoteChar = this.options.nameQuoteCharacter;
+
+        item = `${quoteChar}${item}${quoteChar}`;
+      }
+
+      return item;
+    }
+
+
+    _formatFieldAlias (item) {
+      if (this.options.autoQuoteAliasNames) {
+        let quoteChar = this.options.fieldAliasQuoteCharacter;
+
+        item = `${quoteChar}${item}${quoteChar}`;
+      }
+
+      return item;
+    }
+
+
+    _formatTableAlias (item) {
+      if (this.options.autoQuoteAliasNames) {
+        let quoteChar = this.options.tableAliasQuoteCharacter;
+
+        item = `${quoteChar}${item}${quoteChar}`;
+      }
+
+      return (this.options.useAsForTableAliasNames) 
+        ? `AS ${item}`
+        : item;
+    }
+
+
+    _formatFieldName (item, formattingOptions = {}) {
+      if (this.options.autoQuoteFieldNames) {
+        let quoteChar = this.options.nameQuoteCharacter;
+
+        if (formattingOptions.ignorePeriodsForFieldNameQuotes) {
+          // a.b.c -> `a.b.c`
+          item = `${quoteChar}${item}${quoteChar}`;
+        } else {
+          // a.b.c -> `a`.`b`.`c`
+          item = item
+            .split('.')
+            .map(function(v) {
+              // treat '*' as special case (#79)
+              return ('*' === v ? v : `${quoteChar}${v}${quoteChar}`);
+            })
+            .join('.')
+        }
+      }
+
+      return item;
+    }
+
+
+
+    // Format the given custom value
+    _formatCustomValue (value, asParam = false) {
+      // user defined custom handlers takes precedence
+      let customHandler = 
+        getValueHandler(value, this.options.valueHandlers, cls.globalValueHandlers);
+
+      // use the custom handler if available
+      if (customHandler) {
+        value = customHandler(value, asParam);
+      }
+
+      return value;
+    }
+
+
+
+    /** 
+     * Format given value for inclusion into parameter values array.
+     */
+    _formatValueForParamArray (value) {
+      if (_isArray(value)) {
+        return value.map((v) => {
+          return this._formatValueForParamArray(v);
+        });
+      } else {
+        return this._formatCustomValue(value, true);
+      }
+    }
+
+
+
+    /**
+     * Format the given field value for inclusion into the query string
+     */
+    _formatValueForQueryString (value, formattingOptions = {}) {
+      let customFormattedValue = this._formatCustomValue(value);
+      
+      // if formatting took place then return it directly
+      if (customFormattedValue !== value) {
+        return this._applyNestingFormatting(customFormattedValue);
+      }
+
+      // if it's an array then format each element separately
+      if (_isArray(value)) {
+        value = value.map((v) => {
+          return this._formatValueForQueryString(v);
+        });
+
+        value = this._applyNestingFormatting(value.join(', '));
+      }
+      else {
+        let typeofValue = typeof value;
+
+        if (null === value) {
+          value = "NULL";
+        }
+        else if (typeofValue === "boolean") {
+          value = value ? "TRUE" : "FALSE";
+        }
+        else if (value instanceof cls.BaseBuilder) {
+          value = this._applyNestingFormatting(value.toString());
+        }
+        else if (typeofValue !== "number") {
+          // if it's a string and we have custom string formatting turned on then use that
+          if ('string' === typeofValue && this.options.stringFormatter) {
+            return this.options.stringFormatter(value);
+          }
+
+          if (formattingOptions.dontQuote) {
+            value = `${value}`;
+          } else {
+            let escapedValue = this._escapeValue(value);
+
+            value = `'${escapedValue}'`;
+          }
+        }
+      }
+
+      return value;
+    }
+
+
+    _applyNestingFormatting(str, nesting = true) {
+      if (str && typeof str === 'string' && nesting) {
+        // don't want to apply twice
+        if ('(' !== str.charAt(0) || ')' !== str.charAt(str.length - 1)) {
+          return `(${str})`;
+        }
+      }
+
+      return str;
+    }
+
+
+    /** 
+     * Build given string and its corresponding parameter values into 
+     * output.
+     * 
+     * @param {String} str
+     * @param {Array}  values
+     * @param {Object} [options] Additional options.
+     * @param {Boolean} [options.buildParameterized] Whether to build paramterized string. Default is false.
+     * @param {Boolean} [options.nested] Whether this expression is nested within another.
+     * @param {Boolean} [options.formattingOptions] Formatting options for values in query string.
+     * @return {Object}
+     */
+    _buildString (str, values, options = {}) {
+      let { nested, buildParameterized, formattingOptions } = options;
+
+      values = values || [];
+      str = str || '';
+
+      let formattedStr = '',
+        curValue = -1,
+        formattedValues = [];
+
+      const paramChar = this.options.parameterCharacter;
+
+      let idx = 0;
+
+      while (str.length > idx) {
+        // param char?
+        if (str.substr(idx, paramChar.length) === paramChar) {
+          let value = values[++curValue];
+
+          if (buildParameterized) {
+            if (value instanceof cls.BaseBuilder) {
+              let ret = value._toParamString({
+                buildParameterized: buildParameterized,
+                nested: true,
+              });
+
+              formattedStr += ret.text;
+              formattedValues.push(...ret.values);
+            } else {
+              value = this._formatValueForParamArray(value);
+
+              if (_isArray(value)) {
+                // Array(6) -> "(??, ??, ??, ??, ??, ??)"
+                let tmpStr = value.map(function() {
+                  return paramChar;
+                }).join(', ');
+
+                formattedStr += `(${tmpStr})`;
+
+                formattedValues.push(...value);
+              } else {
+                formattedStr += paramChar;
+
+                formattedValues.push(value);              
+              }
+            }
+          } else {
+            formattedStr += 
+              this._formatValueForQueryString(value, formattingOptions);
+          }
+
+          idx += paramChar.length;
+        } else {
+          formattedStr += str.charAt(idx);
+
+          idx++;
+        }
+      }
+
+      return {
+        text: this._applyNestingFormatting(formattedStr, !!nested),
+        values: formattedValues,
+      };
+    }
+
+
+
+    /** 
+     * Build all given strings and their corresponding parameter values into 
+     * output.
+     * 
+     * @param {Array} strings
+     * @param {Array}  strValues array of value arrays corresponding to each string.
+     * @param {Object} [options] Additional options.
+     * @param {Boolean} [options.buildParameterized] Whether to build paramterized string. Default is false.
+     * @param {Boolean} [options.nested] Whether this expression is nested within another.
+     * @return {Object}
+     */
+    _buildManyStrings(strings, strValues, options = {}) {
+      let totalStr = [],
+        totalValues = [];
+
+      for (let idx = 0; strings.length > idx; ++idx) {
+        let inputString = strings[idx],
+          inputValues = strValues[idx];
+
+        let { text, values } = this._buildString(inputString, inputValues, {
+          buildParameterized: options.buildParameterized,
+          nested: false,
+        });
+
+        totalStr.push(text);
+        totalValues.push(...values);
+      }
+
+      totalStr = totalStr.join(this.options.separator);
+
+      return {
+        text: totalStr.length 
+          ? this._applyNestingFormatting(totalStr, !!options.nested) 
+          : '',
+        values: totalValues,
+      };
+    }
+
+
+
+    /**
+     * Get parameterized representation of this instance.
+     * 
+     * @param {Object} [options] Options.
+     * @param {Boolean} [options.buildParameterized] Whether to build paramterized string. Default is false.
+     * @param {Boolean} [options.nested] Whether this expression is nested within another.
+     * @return {Object}
+     */
+    _toParamString (options) {
+      throw new Error('Not yet implemented');
+    }
+
+
+    /**
+     * Get the expression string.
+     * @return {String}
+     */
+    toString (options = {}) {
+      return this._toParamString(options).text;
+    }
+
+
+    /**
+     * Get the parameterized expression string.
+     * @return {Object}
+     */
+    toParam (options = {}) {
+      return this._toParamString(_extend({}, options, {
+        buildParameterized: true,
+      }));
+    }
+  }
+
+
+
+
+  /*
+  # ---------------------------------------------------------------------------------------------------------
+  # ---------------------------------------------------------------------------------------------------------
+  # cls.Expressions
+  # ---------------------------------------------------------------------------------------------------------
+  # ---------------------------------------------------------------------------------------------------------
+  */
+
+  /**
+   * An SQL expression builder.
+   *
+   * SQL expressions are used in WHERE and ON clauses to filter data by various criteria.
+   *
+   * Expressions can be nested. Nested expression contains can themselves 
+   * contain nested expressions. When rendered a nested expression will be 
+   * fully contained within brackets.
+   * 
+   * All the build methods in this object return the object instance for chained method calling purposes.
+   */
+  cls.Expression = class extends cls.BaseBuilder {
+    // Initialise the expression.
+    constructor (options) {
+      super(options);
+
+      this._nodes = [];
+    }
+
+
+    // Combine the current expression with the given expression using the intersection operator (AND).
+    and (expr, ...params) {
+      expr = this._sanitizeExpression(expr);
+
+      this._nodes.push({
+        type: 'AND',
+        expr: expr,
+        para: params,
+      });
+
+      return this;
+    }
+
+
+
+    // Combine the current expression with the given expression using the union operator (OR).
+    or (expr, ...params) {
+      expr = this._sanitizeExpression(expr);
+
+      this._nodes.push({
+        type: 'OR',
+        expr: expr,
+        para: params,
+      });
+
+      return this;
+    }
+
+
+
+    _toParamString (options = {}) {
+      let totalStr = [],
+        totalValues = [];
+
+      for (let node of this._nodes) {
+        let { type, expr,  para } = node;
+
+
+        let { text, values } = (expr instanceof cls.Expression) 
+          ? expr._toParamString({
+              buildParameterized: options.buildParameterized,
+              nested: true,
+            })
+          : this._buildString(expr, para, {
+              buildParameterized: options.buildParameterized,
+            })
+        ;
+
+        if (totalStr.length) {
+          totalStr.push(type);
+        }
+
+        totalStr.push(text);
+        totalValues.push(...values);
+      }
+
+      totalStr = totalStr.join(' ');
+
+      return {
+        text: this._applyNestingFormatting(totalStr, !!options.nested),
+        values: totalValues,
+      };
+    }
+
+  }
+
+
+ 
+  /*
+  # ---------------------------------------------------------------------------------------------------------
+  # ---------------------------------------------------------------------------------------------------------
+  # cls.Case
+  # ---------------------------------------------------------------------------------------------------------
+  # ---------------------------------------------------------------------------------------------------------
+  */
+
+
+  /**
+   * An SQL CASE expression builder.
+   *
+   * SQL cases are used to select proper values based on specific criteria.
+   */
+  cls.Case = class extends cls.BaseBuilder {
+    constructor (fieldName, options = {}) {
+      super(options);
+
+      if (_isPlainObject(fieldName)) {
+        options = fieldName;
+
+        fieldName = null;
+      }
+
+      if (fieldName) {
+        this._fieldName = this._sanitizeField( fieldName );
+      }
+
+      this.options = _extend({}, cls.DefaultQueryBuilderOptions, options);
+
+      this._cases = [];
+      this._elseValue = null;      
+    }
+
+    when (expression, ...values) {
+      this._cases.unshift({
+        expression: expression,
+        values: values,
+      });
+
+      return this;
+    }
+
+    then (result) {
+      if (this._cases.length == 0) {
+        throw new Error("when() needs to be called first");
+      }
+
+      this._cases[0].result = result;
+      
+      return this;
+    }
+
+    else (elseValue) {
+      this._elseValue = elseValue;
+
+      return this;
+    }
+
+
+    _toParamString (options = {}) {
+      let totalStr = '',
+        totalValues = [];
+
+      for (let { expression, values, result } of this._cases) {
+        totalStr = _pad(totalStr,' ');
+
+        let ret = this._buildString(expression, values, {
+          buildParameterized: options.buildParameterized,
+          nested: true,
+        });
+
+        totalStr += `WHEN ${ret.text} THEN ${this._formatValueForQueryString(result)}`;
+        totalValues.push(...ret.values);
+      }
+
+      if (totalStr.length) {
+        totalStr += ` ELSE ${this._formatValueForQueryString(this._elseValue)} END`;
+
+        if (this._fieldName) {
+          totalStr = `${this._fieldName} ${totalStr}`;
+        }
+
+        totalStr = `CASE ${totalStr}`;
+      } else {
+        totalStr = this._formatValueForQueryString(this._elseValue);
+      }
+
+      return {
+        text: totalStr,
+        values: totalValues,
+      };        
+    }
+  }
+
+
+  /*
+  # ---------------------------------------------------------------------------------------------------------
+  # ---------------------------------------------------------------------------------------------------------
+  # Building blocks
+  # ---------------------------------------------------------------------------------------------------------
+  # ---------------------------------------------------------------------------------------------------------
+  */
+
+  /*
+  # A building block represents a single build-step within a query building process.
+  #
+  # Query builders consist of one or more building blocks which get run in a particular order. Building blocks can
+  # optionally specify methods to expose through the query builder interface. They can access all the input data for
+  # the query builder and manipulate it as necessary, as well as append to the final query string output.
+  #
+  # If you wish to customize how queries get built or add proprietary query phrases and content then it is recommended
+  # that you do so using one or more custom building blocks.
+  #
+  # Original idea posted in https://github.com/hiddentao/export/issues/10#issuecomment-15016427
+  */
+  cls.Block = class extends cls.BaseBuilder {
+    constructor (options) {
+      super(options);
+    }
+
+
+    /**
+    # Get input methods to expose within the query builder.
+    #
+    # By default all methods except the following get returned:
+    #   methods prefixed with _
+    #   constructor and toString()
+    #
+    # @return Object key -> function pairs
+    */
+    exposedMethods () {
+      let ret = {};
+
+      let obj = this;
+
+      while (obj) {
+        Object.getOwnPropertyNames(obj).forEach(function(prop) {
+          if ('constructor' !== prop
+                && typeof obj[prop] === "function" 
+                && prop.charAt(0) !== '_' 
+                && !cls.Block.prototype[prop])
+          {
+            ret[prop] = obj[prop];
+          }
+        });
+
+        obj = Object.getPrototypeOf(obj);
+      };
+      
+      return ret;
+    }
+  }
+
+
+
+  // A fixed string which always gets output
+  cls.StringBlock = class extends cls.Block {
+    constructor (options, str) {
+      super(options);
+
+      this._str = str;
+    }
+
+    _toParamString (options = {}) {
+      return {
+        text: this._str,
+        values: [],
+      };
+    }
+  }
+
+
+
+  // A function string block
+  cls.FunctionBlock = class extends cls.Block {
+    constructor (options) {
+      super(options);
+
+      this._strings = [];
+      this._values = [];
+    } 
+
+    function (str, ...values) {
+      this._strings.push(str);
+      this._values.push(values);
+    }
+
+    _toParamString (options = {}) {
+      return this._buildManyStrings(this._strings, this._values, options);
+    }
+  }
+
+
+  // value handler for FunctionValueBlock objects
+  cls.registerValueHandler(cls.FunctionBlock, function(value, asParam = false) {
+    return asParam ? value.toParam() : value.toString();
+  });
+
+
+  /*
+  # Table specifier base class
+  */
+  cls.AbstractTableBlock = class extends cls.Block {
+    /**
+     * @param {Boolean} [options.singleTable] If true then only allow one table spec.
+     * @param {String} [options.prefix] String prefix for output.
+     */
+    constructor (options, prefix) {
+      super(options);
+
+      this._tables = [];
+    }
+
+    /**
+    # Update given table.
+    #
+    # An alias may also be specified for the table.
+    #
+    # Concrete subclasses should provide a method which calls this
+    */
+    _table (table, alias = null) {
+      alias = alias ? this._sanitizeTableAlias(alias) : alias;
+      table = this._sanitizeTable(table);
+
+      if (this.options.singleTable) {
+        this._tables = [];
+      }
+
+      this._tables.push({
+        table: table,
+        alias: alias,       
+      });
+    }
+
+    // get whether a table has been set
+    _hasTable () {
+      return 0 < this._tables.length;
+    }
+
+    /**
+     * @override
+     */
+    _toParamString (options = {}) {
+      let totalStr = '',
+        totalValues = [];
+
+      if (this._hasTable()) {
+        // retrieve the parameterised queries
+        for (let { table, alias } of this._tables) {
+          totalStr = _pad(totalStr, ', ');
+
+          let tableStr;
+
+          if (table instanceof cls.BaseBuilder) {
+            let { text, values } = table._toParamString({
+              buildParameterized: options.buildParameterized,
+              nested: true,
+            });
+
+            tableStr = text;
+            totalValues.push(...values);            
+          } else {
+            tableStr = this._formatTableName(table);
+          }
+
+          if (alias) {
+            tableStr += ` ${this._formatTableAlias(alias)}`;
+          }
+
+          totalStr += tableStr;
+        }
+
+        if (this.options.prefix) {
+          totalStr = `${this.options.prefix} ${totalStr}`;
+        }
+      }
+
+      return {
+        text: totalStr,
+        values: totalValues,
+      };
+    }
+
+  }
+
+
+
+  // target table for DELETE queries, DELETE <??> FROM
+  cls.TargetTableBlock = class extends cls.AbstractTableBlock {
+    target (table) {
+      this._table(table);
+    }
+  }
+
+
+
+  // Update Table
+  cls.UpdateTableBlock = class extends cls.AbstractTableBlock {
+    table (table, alias = null) {
+      this._table(table, alias);
+    }
+
+    _toParamString (options = {}) {
+      if (!this._hasTable()) {
+        throw new Error("table() needs to be called");
+      }
+
+      return super._toParamString(options);
+    }
+  }
+
+
+  // FROM table
+  cls.FromTableBlock = class extends cls.AbstractTableBlock {
+    constructor (options) {
+      super(_extend({}, options, { 
+        prefix: 'FROM',
+      }));
+    }
+
+    from (table, alias = null) {
+      this._table(table, alias);
+    }
+  }
+
+
+  // INTO table
+  cls.IntoTableBlock = class extends cls.AbstractTableBlock {
+    constructor (options) {
+      super(_extend({}, options, { 
+        prefix: 'INTO',
+        singleTable: true,
+      }));
+    }
+
+    into (table) {
+      this._table(table);
+    }
+
+    _toParamString (options = {}) {
+      if (!this._hasTable()) {
+        throw new Error("into() needs to be called");
+      }
+
+      return super._toParamString(options);
+    }
+  }
+
+
+
+  // (SELECT) Get field
+  cls.GetFieldBlock = class extends cls.Block {
+    constructor (options) {
+      super(options);
+
+      this._fields = [];
+    }
+
+    /**
+    # Add the given fields to the final result set.
+    #
+    # The parameter is an Object containing field names (or database functions) as the keys and aliases for the fields
+    # as the values. If the value for a key is null then no alias is set for that field.
+    #
+    # Internally this method simply calls the field() method of this block to add each individual field.
+    #
+    # options.ignorePeriodsForFieldNameQuotes - whether to ignore period (.) when automatically quoting the field name
+    */
+    fields (_fields, options = {}) {
+      if (_isArray(_fields)) {
+        for (let field of _fields) {
+          this.field(field, null, options);
+        }
+      }
+      else {
+        for (let field in _fields) {
+          let alias = _fields[field];
+
+          this.field(field, alias, options);
+        }
+      }
+    }
+
+    /**
+    # Add the given field to the final result set.
+    #
+    # The 'field' parameter does not necessarily have to be a fieldname. It can use database functions too,
+    # e.g. DATE_FORMAT(a.started, "%H")
+    #
+    # An alias may also be specified for this field.
+    #
+    # options.ignorePeriodsForFieldNameQuotes - whether to ignore period (.) when automatically quoting the field name
+    */
+    field (field, alias = null, options = {}) {
+      alias = alias ? this._sanitizeFieldAlias(alias) : alias;
+      field = this._sanitizeField(field);
+
+      // if field-alias combo already present then don't add
+      let existingField = this._fields.filter((f) => {
+        return f.name === field && f.alias === alias;
+      });
+      if (existingField.length) {
+        return this;
+      }
+
+      this._fields.push({
+        name: field,
+        alias: alias,
+        options: options,
+      });
+    }
+
+
+    _toParamString (options = {}) {
+      let { queryBuilder, buildParameterized } = options;
+
+      let totalStr = '',
+        totalValues = [];
+
+      for (let field of this._fields) {
+        totalStr = _pad(totalStr, ", ");
+        
+        let { name, alias, options } = field;
+
+        if (typeof name === 'string') {
+          totalStr += this._formatFieldName(name, options);
+        } else {
+          let ret = name._toParamString({
+            nested: true,
+            buildParameterized: buildParameterized,
+          });
+
+          totalStr += ret.text;
+          totalValues.push(...ret.values);
+        }
+
+        if (alias) {
+          totalStr += ` AS ${this._formatFieldAlias(alias)}`;
+        }
+      }
+
+      if (!totalStr.length) {
+        // if select query and a table is set then all fields wanted
+        let fromTableBlock = queryBuilder && queryBuilder.getBlock(cls.FromTableBlock);
+        if (fromTableBlock && fromTableBlock._hasTable()) {
+          totalStr = "*";
+        }
+      }
+
+      return {
+        text: totalStr,
+        values: totalValues,
+      }
+    }
+  }
+
+
+
+  // Base class for setting fields to values (used for INSERT and UPDATE queries)
+  cls.AbstractSetFieldBlock = class extends cls.Block {
+    constructor (options) {
+      super(options);
+
+      this._reset();
+    }
+
+    _reset () {
+      this._fields = [];
+      this._values = [[]];
+      this._valueOptions = [[]];
+    }
+
+    // Update the given field with the given value.
+    // This will override any previously set value for the given field.
+    _set (field, value, valueOptions = {}) {
+      if (this._values.length > 1) {
+        throw new Error("Cannot set multiple rows of fields this way.");
+      }
+
+      if (typeof value !== 'undefined') {
+        value = this._sanitizeValue(value);
+      }
+
+      field = this._sanitizeField(field);
+
+      // Explicity overwrite existing fields
+      let index = this._fields.indexOf(field);
+
+      // if field not defined before
+      if (-1 === index) {
+        this._fields.push(field);
+        index = this._fields.length - 1;
+      }
+
+      this._values[0][index] = value;
+      this._valueOptions[0][index] = valueOptions;
+    }
+
+
+    // Insert fields based on the key/value pairs in the given object
+    _setFields (fields, valueOptions = {}) {
+      if (typeof fields !== 'object') {
+        throw new Error("Expected an object but got " + typeof fields);
+      }
+
+      for (let field in fields) {
+        this._set(field, fields[field], valueOptions);
+      }
+    }
+
+    // Insert multiple rows for the given fields. Accepts an array of objects.
+    // This will override all previously set values for every field.
+    _setFieldsRows (fieldsRows, valueOptions = {}) {
+      if (!_isArray(fieldsRows)) {
+        throw new Error("Expected an array of objects but got " + typeof fieldsRows);
+      }
+
+      // Reset the objects stored fields and values
+      this._reset();
+
+      // for each row
+      for (let i = 0; fieldsRows.length > i; ++i) {
+        let fieldRow = fieldsRows[i];
+
+        // for each field
+        for (let field in fieldRow) {
+          let value = fieldRow[field];
+
+          field = this._sanitizeField(field);
+          value = this._sanitizeValue(value);
+
+          let index = this._fields.indexOf(field);
+
+          if (0 < i && -1 === index) {
+            throw new Error('All fields in subsequent rows must match the fields in the first row');
+          }
+
+          // Add field only if it hasn't been added before
+          if (-1 === index) {
+            this._fields.push(field);
+            index = this._fields.length - 1;            
+          }
+
+          // The first value added needs to add the array
+          if (!_isArray(this._values[i])) {
+            this._values[i] = [];
+            this._valueOptions[i] = [];
+          }
+
+          this._values[i][index] = value;
+          this._valueOptions[i][index] = valueOptions;
+        }
+      }
+    }
+  }
+
+
+  // (UPDATE) SET field=value
+  cls.SetFieldBlock = class extends cls.AbstractSetFieldBlock {
+    set (field, value, options) {
+      this._set(field, value, options);
+    }
+
+    setFields (fields, valueOptions) {
+      this._setFields(fields, valueOptions);
+    }
+
+    _toParamString (options = {}) {
+      let { buildParameterized } = options;
+
+      if (0 >= this._fields.length) {
+        throw new Error("set() needs to be called");
+      }
+
+      let totalStr = '',
+        totalValues = [];
+
+      for (let i = 0; i<this._fields.length; ++i) {
+        totalStr = _pad(totalStr, ', ');
+
+        let field = this._formatFieldName(this._fields[i]);
+        let value = this._values[0][i];
+
+        // e.g. field can be an expression such as `count = count + 1`
+        if (0 > field.indexOf('=')) {
+          field = `${field} = ${this.options.parameterCharacter}`;
+        }
+
+        let ret = this._buildString(
+          field,
+          [value],
+          {
+            buildParameterized: buildParameterized,
+            formattingOptions: this._valueOptions[0][i],
+          }
+        );
+
+        totalStr += ret.text;
+        totalValues.push(...ret.values);
+      }
+
+      return { 
+        text: `SET ${totalStr}`, 
+        values: totalValues 
+      };
+    }
+
+  }
+
+
+  // (INSERT INTO) ... field ... value
+  cls.InsertFieldValueBlock = class extends cls.AbstractSetFieldBlock {
+    set (field, value, options = {}) {
+      this._set(field, value, options);
+    }
+
+    setFields (fields, valueOptions) {
+      this._setFields(fields, valueOptions);
+    }
+
+    setFieldsRows (fieldsRows, valueOptions) {
+      this._setFieldsRows(fieldsRows, valueOptions);
+    }
+
+    _toParamString (options = {}) {
+      let { buildParameterized } = options;
+      
+      let fieldString = this._fields
+        .map((f) => this._formatFieldName(f))
+        .join(', ');
+      
+      let valueStrings = [],
+        totalValues = [];
+
+      for (let i = 0; i < this._values.length; ++i) {
+        valueStrings[i] = '';
+
+        for (let j = 0; j < this._values[i].length; ++j) {
+          let ret = 
+            this._buildString(this.options.parameterCharacter, [this._values[i][j]], {
+              buildParameterized: buildParameterized,
+              formattingOptions: this._valueOptions[i][j],
+            });
+
+          totalValues.push(...ret.values);          
+
+          valueStrings[i] = _pad(valueStrings[i], ', ');
+          valueStrings[i] += ret.text;
+        }
+      }
+
+      return { 
+        text: fieldString.length 
+          ? `(${fieldString}) VALUES (${valueStrings.join('), (')})`
+          : '',
+        values: totalValues 
+      };
+    }
+
+  }
+
+
+
+  // (INSERT INTO) ... field ... (SELECT ... FROM ...)
+  cls.InsertFieldsFromQueryBlock = class extends cls.Block {
+    constructor (options) {
+      super(options);
+
+      this._fields = [];
+      this._query = null;
+    }
+
+    fromQuery (fields, selectQuery) {
+      this._fields = fields.map((v) => {
+        return this._sanitizeField(v);
+      });
+
+      this._query = this._sanitizeBaseBuilder(selectQuery);
+    }
+
+    _toParamString (options = {}) {
+      let totalStr = '',
+        totalValues = [];
+
+      if (this._fields.length && this._query) {
+        let { text, values } = this._query._toParamString({
+          buildParameterized: options.buildParameterized,
+          nested: true,
+        });
+
+        totalStr = `(${this._fields.join(', ')}) ${this._applyNestingFormatting(text)}`;
+        totalValues = values;
+      }
+
+      return {
+        text: totalStr,
+        values: totalValues,
+      };
+    }
+  }
+
+
+
+  // DISTINCT
+  cls.DistinctBlock = class extends cls.Block {
+    // Add the DISTINCT keyword to the query.
+    distinct () {
+      this._useDistinct = true;
+    }
+
+    _toParamString () {
+      return {
+        text: this._useDistinct ? "DISTINCT"  : "",
+        values: [],
+      };
+    }
+  }
+
+
+
+  // GROUP BY
+  cls.GroupByBlock = class extends cls.Block {
+    constructor (options) {
+      super(options);
+
+      this._groups = [];
+    }
+
+    // Add a GROUP BY transformation for the given field.
+    group (field) {
+      this._groups.push(this._sanitizeField(field));
+    }
+
+    _toParamString (options = {}) {
+      return {
+        text: this._groups.length ? `GROUP BY ${this._groups.join(', ')}`: '',
+        values: [],
+      };
+    }
+  }
+
+
+  // OFFSET x
+  cls.OffsetBlock = class extends cls.Block {
+    constructor (options) {
+      super(options);
+
+      this._offsets = null;
+    }
+
+    /**
+    # Set the OFFSET transformation.
+    #
+    # Call this will override the previously set offset for this query. Also note that Passing 0 for 'max' will remove
+    # the offset.
+    */
+    offset (start) {
+      this._offsets = this._sanitizeLimitOffset(start);
+    }
+
+
+    _toParamString () {
+      return {
+        text: this._offsets ? `OFFSET ${this._offsets}` : '',
+        values: [],
+      };
+    }
+  }
+
+
+  // Abstract condition base class
+  cls.AbstractConditionBlock = class extends cls.Block {
+    /** 
+     * @param {String} options.verb The condition verb.
+     */
+    constructor (options) {
+      super(options);
+
+      this._conditions = [];
+    }
+
+    /**
+    # Add a condition.
+    #
+    # When the final query is constructed all the conditions are combined using the intersection (AND) operator.
+    #
+    # Concrete subclasses should provide a method which calls this
+    */
+    _condition (condition, ...values) {
+      condition = this._sanitizeExpression(condition);
+
+      this._conditions.push({
+        expr: condition,
+        values: values,
+      });
+    }
+
+
+    _toParamString (options = {}) {
+      let totalStr = [],
+        totalValues = [];
+
+      for (let { expr, values } of this._conditions) {
+        let ret = (expr instanceof cls.Expression) 
+          ? expr._toParamString({
+              buildParameterized: options.buildParameterized,
+            })
+          : this._buildString(expr, values, {
+              buildParameterized: options.buildParameterized,
+            })
+        ;
+
+        if (ret.text.length) {
+          totalStr.push(ret.text);
+        }
+
+        totalValues.push(...ret.values);
+      }
+
+      if (totalStr.length) {
+        totalStr = totalStr.join(') AND (');
+      }
+
+      return {
+        text: totalStr.length ? `${this.options.verb} (${totalStr})` : '',
+        values: totalValues,
+      };
+    }
+  }
+
+
+  // WHERE
+  cls.WhereBlock = class extends cls.AbstractConditionBlock {
+    constructor (options) {
+      super(_extend({}, options, {
+        verb: 'WHERE'
+      }));
+    }
+
+    where (condition, ...values) {
+      this._condition(condition, ...values);
+    }
+  }
+
+
+  // HAVING
+  cls.HavingBlock = class extends cls.AbstractConditionBlock {
+    constructor(options) {
+      super(_extend({}, options, {
+        verb: 'HAVING'
+      }));
+    }
+
+    having (condition, ...values) {
+      this._condition(condition, ...values);
+    }
+  }
+
+
+  // ORDER BY
+  cls.OrderByBlock = class extends cls.Block {
+    constructor (options) {
+      super(options);
+
+      this._orders = [];
+    }
+
+    /**
+    # Add an ORDER BY transformation for the given field in the given order.
+    #
+    # To specify descending order pass false for the 'asc' parameter.
+    */
+    order (field, asc, ...values) {
+      field = this._sanitizeField(field);
+
+      asc = (asc === undefined) ? true : asc;
+      asc = (asc !== null) ? !!asc : asc;
+
+      this._orders.push({
+        field: field,
+        dir: asc,   
+        values: values,
+      });
+    }
+
+    _toParamString (options = {}) {
+      let totalStr = '',
+        totalValues = [];
+
+      for (let {field, dir, values} of this._orders) {
+        totalStr = _pad(totalStr, ', ');
+
+        let ret = this._buildString(field, values, {
+          buildParameterized: options.buildParameterized,
+        });
+
+        totalStr += ret.text,
+        totalValues.push(...ret.values);
+
+        if (dir !== null) {
+          totalStr += ` ${dir ? 'ASC' : 'DESC'}`;
+        }
+      }
+
+      return {
+        text: totalStr.length ? `ORDER BY ${totalStr}` : '',
+        values: totalValues,
+      };
+    }
+  }
+
+
+
+  // LIMIT
+  cls.LimitBlock = class extends cls.Block {
+    constructor (options) {
+      super(options);
+
+      this._limit = null;
+    }
+
+    /**
+    # Set the LIMIT transformation.
+    #
+    # Call this will override the previously set limit for this query. Also note that Passing 0 for 'max' will remove
+    # the limit.
+    */
+    limit (limit) {
+      this._limit = this._sanitizeLimitOffset(limit);
+    }
+
+
+    _toParamString () {
+      return {
+        text: (null !== this._limit) ? `LIMIT ${this._limit}` : '',
+        values: [],
+      };
+    }
+  }
+
+
+
+  // JOIN
+  cls.JoinBlock = class extends cls.Block {
+    constructor (options) {
+      super(options);
+
+      this._joins = [];
+    }
+
+    /**
+    # Add a JOIN with the given table.
+    #
+    # 'table' is the name of the table to join with.
+    #
+    # 'alias' is an optional alias for the table name.
+    #
+    # 'condition' is an optional condition (containing an SQL expression) for the JOIN.
+    #
+    # 'type' must be either one of INNER, OUTER, LEFT or RIGHT. Default is 'INNER'.
+    #
+    */
+    join (table, alias = null, condition = null, type = 'INNER') {
+      table = this._sanitizeTable(table, true);
+      alias = alias ? this._sanitizeTableAlias(alias) : alias;
+      condition = condition ? this._sanitizeExpression(condition) : condition;
+
+      this._joins.push({
+        type: type,
+        table: table,
+        alias: alias,
+        condition: condition,
+      });
+    }
+
+    left_join (table, alias = null, condition = null) {
+      this.join(table, alias, condition, 'LEFT');
+    }
+
+    right_join (table, alias = null, condition = null) {
+      this.join(table, alias, condition, 'RIGHT');
+    }
+
+    outer_join (table, alias = null, condition = null) {
+      this.join(table, alias, condition, 'OUTER');
+    }
+
+    left_outer_join (table, alias = null, condition = null) {
+      this.join(table, alias, condition, 'LEFT OUTER');
+    }
+
+    full_join (table, alias = null, condition = null) {
+      this.join(table, alias, condition, 'FULL');
+    }
+
+    cross_join (table, alias = null, condition = null) {
+      this.join(table, alias, condition, 'CROSS');
+    }
+
+
+    _toParamString (options = {}) {
+      let totalStr = "",  
+        totalValues = [];
+
+      for (let {type, table, alias, condition} of this._joins) {
+        totalStr = _pad(totalStr, this.options.separator);
+
+        let tableStr;
+
+        if (table instanceof cls.BaseBuilder) {
+          let ret = table._toParamString({
+            buildParameterized: options.buildParameterized,
+            nested: true
+          });
+
+          totalValues.push(...ret.values);          
+          tableStr = ret.text;
+        } else {
+          tableStr = this._formatTableName(table);
+        }
+
+        totalStr += `${type} JOIN ${tableStr}`;
+
+        if (alias) {
+          totalStr += ` ${this._formatTableAlias(alias)}`;
+        }
+
+        if (condition) {
+          totalStr += ' ON ';
+
+          let ret;
+
+          if (condition instanceof cls.Expression) {
+            ret = condition._toParamString({
+              buildParameterized: options.buildParameterized,
+            });
+          } else {
+            ret = this._buildString(condition, [], {
+              buildParameterized: options.buildParameterized,
+            });
+          }
+
+          totalStr += this._applyNestingFormatting(ret.text);
+          totalValues.push(...ret.values);
+        }
+      }
+
+      return {
+        text: totalStr,
+        values: totalValues,
+      };
+    }
+  }
+
+
+  // UNION
+  cls.UnionBlock = class extends cls.Block {
+    constructor (options) {
+      super(options);
+
+      this._unions = [];
+    }
+
+    /**
+    # Add a UNION with the given table/query.
+    #
+    # 'table' is the name of the table or query to union with.
+    #
+    # 'type' must be either one of UNION or UNION ALL.... Default is 'UNION'.
+    */
+    union (table, type = 'UNION') {
+      table = this._sanitizeTable(table);
+
+      this._unions.push({
+        type: type,
+        table: table,
+      });
+    }
+
+    // Add a UNION ALL with the given table/query.
+    union_all (table) {
+      this.union(table, 'UNION ALL');
+    }
+
+
+    _toParamString (options = {}) {
+      let totalStr = '',
+        totalValues = [];
+
+      for (let {type, table} of this._unions) {
+        totalStr = _pad(totalStr, this.options.separator);
+
+        let tableStr;
+
+        if (table instanceof cls.BaseBuilder) {
+          let ret = table._toParamString({
+            buildParameterized: options.buildParameterized,
+            nested: true
+          });
+
+          tableStr = ret.text;
+          totalValues.push(...ret.values);
+        } else {
+          totalStr = this._formatTableName(table);
+        }
+
+        totalStr += `${type} ${tableStr}`;
+      }
+
+      return {
+        text: totalStr,
+        values: totalValues,
+      };
+    }
+  }
+
+
+  /*
+  # ---------------------------------------------------------------------------------------------------------
+  # ---------------------------------------------------------------------------------------------------------
+  # Query builders
+  # ---------------------------------------------------------------------------------------------------------
+  # ---------------------------------------------------------------------------------------------------------
+  */
+
+  /**
+  # Query builder base class
+  #
+  # Note that the query builder does not check the final query string for correctness.
+  #
+  # All the build methods in this object return the object instance for chained method calling purposes.
+  */
+  cls.QueryBuilder = class extends cls.BaseBuilder {
+    /**
+    # Constructor
+    #
+    # blocks - array of cls.BaseBuilderBlock instances to build the query with.
+    */
+    constructor (options, blocks) {
+      super(options);
+
+      this.blocks = blocks || [];
+
+      // Copy exposed methods into myself
+      for (let block of this.blocks) {
+        let exposedMethods = block.exposedMethods();
+
+        for (let methodName in exposedMethods) {
+          let methodBody = exposedMethods[methodName];
+
+          if (undefined !== this[methodName]) {
+            throw new Error(`Builder already has a builder method called: ${methodName}`);
+          }
+
+          ((block, name, body) => {
+            this[name] = (...args) => {
+              body.call(block, ...args);
+
+              return this;
+            };
+          })(block, methodName, methodBody);
+        }
+      }
+    }
+
+
+    /**
+    # Register a custom value handler for this query builder and all its contained blocks.
+    #
+    # Note: This will override any globally registered handler for this value type.
+    */
+    registerValueHandler (type, handler) {
+      for (let block of this.blocks) {
+        block.registerValueHandler(type, handler);
+      }
+      
+      super.registerValueHandler(type, handler);
+
+      return this;
+    }
+
+    /**
+    # Update query builder options
+    #
+    # This will update the options for all blocks too. Use this method with caution as it allows you to change the
+    # behaviour of your query builder mid-build.
+    */
+    updateOptions (options) {
+      this.options = _extend({}, this.options, options);
+
+      for (let block of this.blocks) {
+        block.options = _extend({}, block.options, options);
+      }
+    }
+
+
+
+    // Get the final fully constructed query param obj.
+    _toParamString (options = {}) {
+      options = _extend({}, this.options, options);
+
+      let blockResults = this.blocks.map((b) => b._toParamString({
+        buildParameterized: options.buildParameterized,
+        queryBuilder: this,
+      }));
+
+      let blockTexts = blockResults.map((b) => b.text);
+      let blockValues = blockResults.map((b) => b.values);
+
+      let totalStr = blockTexts
+        .filter((v) => (0 < v.length))
+        .join(options.separator);
+
+      let totalValues = [].concat(...blockValues);
+
+      if (!options.nested) {
+        if (options.numberedParameters) {
+          let i = (undefined !== options.numberedParametersStartAt) 
+            ? options.numberedParametersStartAt 
+            : 1;
+
+          // construct regex for searching
+          const regex = options.parameterCharacter.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
+
+          totalStr = totalStr.replace(
+            new RegExp(regex, 'g'), 
+            function() {
+              return `${options.numberedParametersPrefix}${i++}`;
+            }
+          );
+        }
+      }
+
+      return {
+        text: this._applyNestingFormatting(totalStr, !!options.nested),
+        values: totalValues,
+      };
+    }
+
+    // Deep clone
+    clone () {
+      let blockClones = this.blocks.map((v) => {
+        return v.clone();
+      });
+
+      return new this.constructor(this.options, blockClones);
+    }
+
+    // Get a specific block
+    getBlock (blockType) {
+      let filtered = this.blocks.filter(b => b instanceof blockType);
+
+      return filtered[0];
+    }
+  }
+
+
+  // SELECT query builder.
+  cls.Select = class extends cls.QueryBuilder {
+    constructor (options, blocks = null) {
+      blocks = blocks || [
+        new cls.StringBlock(options, 'SELECT'),
+        new cls.FunctionBlock(options),
+        new cls.DistinctBlock(options),
+        new cls.GetFieldBlock(options),
+        new cls.FromTableBlock(options),
+        new cls.JoinBlock(options),
+        new cls.WhereBlock(options),
+        new cls.GroupByBlock(options),
+        new cls.HavingBlock(options),
+        new cls.OrderByBlock(options),
+        new cls.LimitBlock(options),
+        new cls.OffsetBlock(options),
+        new cls.UnionBlock(options),
+      ];
+
+      super(options, blocks);
+    } 
+  }
+
+
+
+  // UPDATE query builder.
+  cls.Update = class extends cls.QueryBuilder {
+    constructor (options, blocks = null) {
+      blocks = blocks || [
+        new cls.StringBlock(options, 'UPDATE'),
+        new cls.UpdateTableBlock(options),
+        new cls.SetFieldBlock(options),
+        new cls.WhereBlock(options),
+        new cls.OrderByBlock(options),
+        new cls.LimitBlock(options),
+      ];
+
+      super(options, blocks);
+    }
+  }
+
+
+
+
+
+  // DELETE query builder.
+  cls.Delete = class extends cls.QueryBuilder {
+    constructor (options, blocks = null) {
+      blocks = blocks || [
+        new cls.StringBlock(options, 'DELETE'),
+        new cls.TargetTableBlock(options),
+        new cls.FromTableBlock(_extend({}, options, {
+          singleTable: true
+        })),
+        new cls.JoinBlock(options),
+        new cls.WhereBlock(options),
+        new cls.OrderByBlock(options),
+        new cls.LimitBlock(options),
+      ];
+
+      super(options, blocks);
+    }
+  }
+
+
+
+
+
+  // An INSERT query builder.
+  cls.Insert = class extends cls.QueryBuilder {
+    constructor (options, blocks = null) {
+      blocks = blocks || [
+        new cls.StringBlock(options, 'INSERT'),
+        new cls.IntoTableBlock(options),
+        new cls.InsertFieldValueBlock(options),
+        new cls.InsertFieldsFromQueryBlock(options),
+      ];
+
+      super(options, blocks);
+    }
+  }
+
+
+  let _squel = {
+    VERSION: '<<VERSION_STRING>>',
+    flavour: flavour,
+    expr: function(options) {
+      return new cls.Expression(options);
+    },
+    case: function(name, options) {
+      return new cls.Case(name, options);
+    },
+    select: function(options, blocks) {
+      return new cls.Select(options, blocks);
+    },
+    update: function(options, blocks) {
+      return new cls.Update(options, blocks);
+    },
+    insert: function(options, blocks) {
+      return new cls.Insert(options, blocks);
+    },
+    delete: function(options, blocks) {
+      return new cls.Delete(options, blocks);
+    },
+    str: function(...args) {
+      let inst = new cls.FunctionBlock();
+      inst.function(...args);
+      return inst;
+    },
+    registerValueHandler: cls.registerValueHandler,
+  };
+
+  // aliases
+  _squel.remove = _squel.delete;
+
+  // classes
+  _squel.cls = cls;
+
+
+  return _squel;
+}
+
+
+/**
+# ---------------------------------------------------------------------------------------------------------
+# ---------------------------------------------------------------------------------------------------------
+# Exported instance (and for use by flavour definitions further down).
+# ---------------------------------------------------------------------------------------------------------
+# ---------------------------------------------------------------------------------------------------------
+*/
+
+let squel = _buildSquel();
+
+
+/**
+# ---------------------------------------------------------------------------------------------------------
+# ---------------------------------------------------------------------------------------------------------
+# Squel SQL flavours
+# ---------------------------------------------------------------------------------------------------------
+# ---------------------------------------------------------------------------------------------------------
+*/
+
+// Available flavours
+squel.flavours = {};
+
+// Setup Squel for a particular SQL flavour
+squel.useFlavour = function(flavour = null) {
+  if (!flavour) {
+    return squel;
+  }
+
+  if (squel.flavours[flavour] instanceof Function) {
+    let s = _buildSquel(flavour);
+
+    squel.flavours[flavour].call(null, s);
+
+    // add in flavour methods
+    s.flavours = squel.flavours;
+    s.useFlavour = squel.useFlavour;
+
+    return s;
+  }
+  else {
+    throw new Error(`Flavour not available: ${flavour}`);
+  }
+}
+
+

+ 316 - 0
www/lib/squel/src/mssql.js

@@ -0,0 +1,316 @@
+squel.flavours['mssql'] = function(_squel) {
+  let cls = _squel.cls;
+
+  cls.DefaultQueryBuilderOptions.replaceSingleQuotes = true;
+  cls.DefaultQueryBuilderOptions.autoQuoteAliasNames = false;
+  cls.DefaultQueryBuilderOptions.numberedParametersPrefix = '@';
+
+
+  _squel.registerValueHandler(Date, function(date) {
+    return `'${date.getUTCFullYear()}-${date.getUTCMonth()+1}-${date.getUTCDate()} ${date.getUTCHours()}:${date.getUTCMinutes()}:${date.getUTCSeconds()}'`;
+  });
+
+
+  // LIMIT,  OFFSET x and TOP x
+  cls.MssqlLimitOffsetTopBlock = class extends cls.Block {
+    constructor (options) {
+      super(options);
+      this._limits = null;
+      this._offsets = null;
+
+      // This is setup as one block to return many as they all have to use each others data at different times
+      // The build String of EITHER LIMIT OR TOP should execute, never both.
+
+      /**
+      # Set the LIMIT/TOP transformation.
+      #
+      # Call this will override the previously set limit for this query. Also note that Passing 0 for 'max' will remove
+      # the limit.
+      */
+      let _limit = function(max) {
+        max = this._sanitizeLimitOffset(max);
+        this._parent._limits = max;
+      };
+
+      this.ParentBlock = class extends cls.Block {
+        constructor (parent) {
+          super(parent.options);
+          this._parent = parent;
+        }
+      };
+
+      this.LimitBlock = class extends this.ParentBlock {
+        constructor (parent) {
+          super(parent);
+          this.limit = _limit;
+        }
+
+        _toParamString () {
+          let str = "";
+
+          if (this._parent._limits && this._parent._offsets) {
+            str = `FETCH NEXT ${this._parent._limits} ROWS ONLY`;
+          }
+
+          return {
+            text: str,
+            values: [],
+          };
+        }
+      };
+
+      this.TopBlock = class extends this.ParentBlock {
+        constructor (parent) {
+          super(parent);
+          this.top = _limit;
+        }
+        _toParamString () {
+          let str = "";
+
+          if (this._parent._limits && !this._parent._offsets) {
+            str = `TOP (${this._parent._limits})`;
+          }
+
+          return {
+            text: str,
+            values: [],
+          }
+        }
+      };
+
+      this.OffsetBlock = class extends this.ParentBlock {
+        offset (start) {
+          this._parent._offsets = this._sanitizeLimitOffset(start);
+        }
+
+        _toParamString () {
+          let str = "";
+
+          if (this._parent._offsets) {
+            str = `OFFSET ${this._parent._offsets} ROWS`; 
+          }
+
+          return {
+            text: str,
+            values: [],
+          }
+        }
+      };
+    }
+
+    LIMIT () {
+      return new this.LimitBlock(this);
+    }
+
+    TOP () {
+      return new this.TopBlock(this);
+    }
+
+    OFFSET () {
+      return new this.OffsetBlock(this);
+    }
+  };
+
+
+  cls.MssqlUpdateTopBlock = class extends cls.Block {
+    constructor (options) {
+      super(options);
+      this._limits = null;
+
+      this.limit = this.top = (max) => {
+        this._limits = this._sanitizeLimitOffset(max);
+      };
+    }
+
+    _toParamString () {
+      return {
+        text: (this._limits) ? `TOP (${this._limits})` : "",
+        values: [],
+      };
+    }
+  };
+
+
+  cls.MssqlInsertFieldValueBlock = class extends cls.InsertFieldValueBlock {
+    constructor (options) {
+      super(options);
+      this._outputs = [];
+    }
+
+    // add fields to the output clause
+    output (fields) {
+      if ('string' === typeof fields) {
+        this._outputs.push(`INSERTED.${this._sanitizeField(fields)}`);
+      } else {
+        fields.forEach((f) => {
+          this._outputs.push(`INSERTED.${this._sanitizeField(f)}`);
+        });
+      }
+    }
+
+    _toParamString (options) {
+      let ret = super._toParamString(options);
+
+      if (ret.text.length && 0 < this._outputs.length) {
+        let innerStr = `OUTPUT ${this._outputs.join(', ')} `;
+
+        let valuesPos = ret.text.indexOf('VALUES');
+
+        ret.text = ret.text.substr(0, valuesPos) + innerStr + ret.text.substr(valuesPos);
+      }
+
+      return ret;
+    }
+  };
+
+
+  cls.MssqlUpdateDeleteOutputBlock = class extends cls.Block {
+    constructor (options) {
+      super(options);
+      this._outputs = [];
+    }
+
+
+    /**
+    # Add the given fields to the final result set.
+    #
+    # The parameter is an Object containing field names (or database functions) as the keys and aliases for the fields
+    # as the values. If the value for a key is null then no alias is set for that field.
+    #
+    # Internally this method simply calls the field() method of this block to add each individual field.
+    */
+    outputs (outputs) {
+      for (let output in outputs) {
+        this.output(output, outputs[output]);
+      }
+    }
+
+    /**
+    # Add the given field to the final result set.
+    #
+    # The 'field' parameter does not necessarily have to be a fieldname. It can use database functions too,
+    # e.g. DATE_FORMAT(a.started, "%H")
+    #
+    # An alias may also be specified for this field.
+    */
+    output (output, alias = null) {
+      output = this._sanitizeField(output);
+      alias = alias ? this._sanitizeFieldAlias(alias) : alias;
+
+      this._outputs.push({
+        name: this.options.forDelete ? `DELETED.${output}` : `INSERTED.${output}`,
+        alias: alias,
+      });
+    }
+
+
+    _toParamString (queryBuilder) {
+      let totalStr = "";
+
+      if (this._outputs.length) {
+        for (let output of this._outputs) {
+          totalStr = _pad(totalStr, ", ");
+
+          totalStr += output.name;
+
+          if (output.alias) {
+            totalStr += ` AS ${this._formatFieldAlias(output.alias)}`;
+          }
+        }
+
+        totalStr = `OUTPUT ${totalStr}`;
+      }
+
+      return {
+        text: totalStr,
+        values: [],
+      };
+    }
+  }
+
+
+  // SELECT query builder.
+  cls.Select = class extends cls.QueryBuilder {
+    constructor (options, blocks = null) {
+      let limitOffsetTopBlock = new cls.MssqlLimitOffsetTopBlock(options);
+
+      blocks = blocks || [
+        new cls.StringBlock(options, 'SELECT'),
+        new cls.DistinctBlock(options),
+        limitOffsetTopBlock.TOP(),
+        new cls.GetFieldBlock(options),
+        new cls.FromTableBlock(options),
+        new cls.JoinBlock(options),
+        new cls.WhereBlock(options),
+        new cls.GroupByBlock(options),
+        new cls.OrderByBlock(options),
+        limitOffsetTopBlock.OFFSET(),
+        limitOffsetTopBlock.LIMIT(),
+        new cls.UnionBlock(options),
+      ];
+
+      super(options, blocks);
+    }
+  }
+
+
+
+  // Order By in update requires subquery
+
+  // UPDATE query builder.
+  cls.Update = class extends cls.QueryBuilder {
+    constructor (options, blocks = null) {
+      blocks = blocks || [
+        new cls.StringBlock(options, 'UPDATE'),
+        new cls.MssqlUpdateTopBlock(options),
+        new cls.UpdateTableBlock(options),
+        new cls.SetFieldBlock(options),
+        new cls.MssqlUpdateDeleteOutputBlock(options),
+        new cls.WhereBlock(options),
+      ];
+
+      super(options, blocks);
+    }
+  }
+
+
+
+  // Order By and Limit/Top in delete requires subquery
+
+  // DELETE query builder.
+  cls.Delete = class extends cls.QueryBuilder {
+    constructor (options, blocks = null) {
+      blocks = blocks || [
+        new cls.StringBlock(options, 'DELETE'),
+        new cls.TargetTableBlock(options),
+        new cls.FromTableBlock( _extend({}, options, { singleTable: true }) ),
+        new cls.JoinBlock(options),
+        new cls.MssqlUpdateDeleteOutputBlock(_extend({}, options, { forDelete: true })),
+        new cls.WhereBlock(options),
+        new cls.OrderByBlock(options),
+        new cls.LimitBlock(options),
+      ];
+
+      super(options, blocks);
+    }
+  }
+
+
+  // An INSERT query builder.
+  cls.Insert = class extends cls.QueryBuilder {
+    constructor (options, blocks = null) {
+      blocks = blocks || [
+        new cls.StringBlock(options, 'INSERT'),
+        new cls.IntoTableBlock(options),
+        new cls.MssqlInsertFieldValueBlock(options),
+        new cls.InsertFieldsFromQueryBlock(options),
+      ];
+
+      super(options, blocks);
+    }
+  }
+
+
+
+
+}

+ 68 - 0
www/lib/squel/src/mysql.js

@@ -0,0 +1,68 @@
+// This file contains additional Squel commands for use with MySQL
+
+squel.flavours['mysql'] = function(_squel) {
+  let cls = _squel.cls;
+
+  // ON DUPLICATE KEY UPDATE ...
+  cls.MysqlOnDuplicateKeyUpdateBlock = class extends cls.AbstractSetFieldBlock {
+    onDupUpdate (field, value, options) {
+      this._set(field, value, options);
+    }
+
+    _toParamString (options = {}) {
+      let totalStr = "",
+        totalValues = [];
+
+      for (let i = 0; i < this._fields.length; ++i) {
+        totalStr = _pad(totalStr, ', ');
+
+        let field = this._fields[i];
+
+        let value = this._values[0][i];
+
+        let valueOptions = this._valueOptions[0][i];
+
+        // e.g. if field is an expression such as: count = count + 1
+        if (typeof value === 'undefined') {
+          totalStr += field;
+        } else {
+          let ret = this._buildString(
+            `${field} = ${this.options.parameterCharacter}`,
+            [value],
+            {
+              buildParameterized: options.buildParameterized,
+              formattingOptions: valueOptions,
+            }
+          );
+
+          totalStr += ret.text;
+          totalValues.push(...ret.values);
+        }
+      }
+
+      return {
+        text: !totalStr.length ? "" : `ON DUPLICATE KEY UPDATE ${totalStr}`,
+        values: totalValues,
+      };
+    }
+  }
+
+
+  // INSERT query builder.
+  cls.Insert = class extends cls.QueryBuilder {
+    constructor (options, blocks = null) {
+      blocks = blocks || [
+        new cls.StringBlock(options, 'INSERT'),
+        new cls.IntoTableBlock(options),
+        new cls.InsertFieldValueBlock(options),
+        new cls.InsertFieldsFromQueryBlock(options),
+        new cls.MysqlOnDuplicateKeyUpdateBlock(options),
+      ];
+
+      super(options, blocks);
+    }
+  }
+
+
+};
+

+ 175 - 0
www/lib/squel/src/postgres.js

@@ -0,0 +1,175 @@
+// This file contains additional Squel commands for use with the Postgres DB engine
+squel.flavours['postgres'] = function(_squel) {
+  let cls = _squel.cls;
+
+  cls.DefaultQueryBuilderOptions.numberedParameters = true;
+  cls.DefaultQueryBuilderOptions.numberedParametersStartAt = 1;
+  cls.DefaultQueryBuilderOptions.autoQuoteAliasNames = false;
+  cls.DefaultQueryBuilderOptions.useAsForTableAliasNames = true;
+
+  // RETURNING
+  cls.ReturningBlock = class extends cls.Block {
+    constructor (options) {
+      super(options);
+      this._str = null;
+    }
+
+    returning (ret) {
+      this._str = this._sanitizeField(ret);
+    }
+
+    _toParamString () {
+      return {
+        text: this._str ? `RETURNING ${this._str}` : '',
+        values: [],
+      }
+    }
+  }
+
+  // WITH
+  cls.WithBlock = class extends cls.Block {
+    constructor (options) {
+      super(options);
+      this._tables = [];
+    }
+
+    with (alias, table) {
+      this._tables.push({alias, table});
+    }
+
+    _toParamString(options = {}) {
+      var parts  = [];
+      var values = [];
+
+      for (let {alias, table} of this._tables) {
+        let ret = table._toParamString({
+          buildParameterized: options.buildParameterized,
+          nested: true
+        });
+
+        parts.push(`${alias} AS ${ret.text}`);
+        values.push(...ret.values);
+      }
+
+      return {
+        text: parts.length ? `WITH ${parts.join(', ')}` : '',
+        values
+      };
+    }
+  }
+
+  // DISTINCT [ON]
+  cls.DistinctOnBlock = class extends cls.Block {
+    constructor(options) {
+      super(options);
+
+      this._distinctFields = [];
+    }
+
+    distinct(...fields) {
+      this._useDistinct = true;
+
+      // Add all fields to the DISTINCT ON clause.
+      fields.forEach((field) => {
+        this._distinctFields.push(this._sanitizeField(field));
+      });
+    }
+
+    _toParamString() {
+      let text = '';
+
+      if (this._useDistinct) {
+        text = 'DISTINCT';
+
+        if (this._distinctFields.length) {
+            text += ` ON (${this._distinctFields.join(', ')})`;
+        }
+      }
+
+      return {
+        text,
+        values: []
+      };
+    }
+  }
+
+  // SELECT query builder.
+  cls.Select = class extends cls.QueryBuilder {
+    constructor (options, blocks = null) {
+      blocks = blocks || [
+        new cls.WithBlock(options),
+        new cls.StringBlock(options, 'SELECT'),
+        new cls.FunctionBlock(options),
+        new cls.DistinctOnBlock(options),
+        new cls.GetFieldBlock(options),
+        new cls.FromTableBlock(options),
+        new cls.JoinBlock(options),
+        new cls.WhereBlock(options),
+        new cls.GroupByBlock(options),
+        new cls.HavingBlock(options),
+        new cls.OrderByBlock(options),
+        new cls.LimitBlock(options),
+        new cls.OffsetBlock(options),
+        new cls.UnionBlock(options)
+      ];
+
+      super(options, blocks);
+    }
+  }
+
+  // INSERT query builder
+  cls.Insert = class extends cls.QueryBuilder {
+    constructor (options, blocks = null) {
+      blocks = blocks || [
+        new cls.WithBlock(options),
+        new cls.StringBlock(options, 'INSERT'),
+        new cls.IntoTableBlock(options),
+        new cls.InsertFieldValueBlock(options),
+        new cls.InsertFieldsFromQueryBlock(options),
+        new cls.ReturningBlock(options),
+      ];
+
+      super(options, blocks);
+    }
+  }
+
+  // UPDATE query builder
+  cls.Update = class extends cls.QueryBuilder {
+    constructor (options, blocks = null) {
+      blocks = blocks || [
+        new cls.WithBlock(options),
+        new cls.StringBlock(options, 'UPDATE'),
+        new cls.UpdateTableBlock(options),
+        new cls.SetFieldBlock(options),
+        new cls.FromTableBlock(options),
+        new cls.WhereBlock(options),
+        new cls.OrderByBlock(options),
+        new cls.LimitBlock(options),
+        new cls.ReturningBlock(options),
+      ];
+
+      super(options, blocks);
+    }
+  }
+
+  // DELETE query builder
+  cls.Delete = class extends cls.QueryBuilder {
+    constructor (options, blocks = null) {
+      blocks = blocks || [
+        new cls.WithBlock(options),
+        new cls.StringBlock(options, 'DELETE'),
+        new cls.TargetTableBlock(options),
+        new cls.FromTableBlock(_extend({}, options, {
+          singleTable: true
+        })),
+        new cls.JoinBlock(options),
+        new cls.WhereBlock(options),
+        new cls.OrderByBlock(options),
+        new cls.LimitBlock(options),
+        new cls.ReturningBlock(options),
+      ];
+
+      super(options, blocks);
+    }
+  }
+}

+ 1147 - 0
www/lib/squel/test/baseclasses.test.coffee

@@ -0,0 +1,1147 @@
+###
+Copyright (c) 2014 Ramesh Nair (hiddentao.com)
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+###
+
+
+squel = require "../dist/squel-basic"
+{_, testCreator, assert, expect, should} = require './testbase'
+test = testCreator()
+
+test['Version number'] =
+  assert.same squel.VERSION, require('../package.json').version
+
+test['Default flavour'] =
+  assert.isNull squel.flavour
+
+test['internal methods'] = 
+  '_getObjectClassName': ->
+    s = 'a string'
+    b = new Object()
+    c = new Error()
+    d = 1
+
+    assert.same squel.cls._getObjectClassName(0), undefined
+    assert.same squel.cls._getObjectClassName(true), 'Boolean'
+    assert.same squel.cls._getObjectClassName(1.2), 'Number'
+    assert.same squel.cls._getObjectClassName('a string'), 'String'
+    assert.same squel.cls._getObjectClassName(new Object), 'Object'
+    assert.same squel.cls._getObjectClassName(new Error), 'Error'
+
+
+test['Cloneable base class'] =
+  '>> clone()': ->
+
+    class Child extends squel.cls.Cloneable
+      constructor: ->
+        @a = 1
+        @b = 2.2
+        @c = true
+        @d = 'str'
+        @e = [1]
+        @f = { a: 1 }
+
+    child = new Child()
+
+    copy = child.clone()
+    assert.instanceOf copy, Child
+
+    child.a = 2
+    child.b = 3.2
+    child.c = false
+    child.d = 'str2'
+    child.e.push(2)
+    child.f.b = 1
+
+    assert.same copy.a, 1
+    assert.same copy.b, 2.2
+    assert.same copy.c, true
+    assert.same copy.d, 'str'
+    assert.same copy.e, [1]
+    assert.same copy.f, { a: 1 }
+
+
+test['Default query builder options'] =
+  'default options': ->
+    assert.same {
+      autoQuoteTableNames: false
+      autoQuoteFieldNames: false
+      autoQuoteAliasNames: true
+      useAsForTableAliasNames: false
+      nameQuoteCharacter: '`'
+      tableAliasQuoteCharacter: '`'
+      fieldAliasQuoteCharacter: '"'
+      valueHandlers: []
+      parameterCharacter: '?'
+      numberedParameters: false
+      numberedParametersPrefix: '$'
+      numberedParametersStartAt: 1
+      replaceSingleQuotes: false
+      singleQuoteReplacement: '\'\''
+      separator: ' '
+      stringFormatter: null
+    }, squel.cls.DefaultQueryBuilderOptions
+
+
+
+test['Register global custom value handler'] =
+  'beforeEach': ->
+    @originalHandlers = [].concat(squel.cls.globalValueHandlers)
+    squel.cls.globalValueHandlers = []
+  'afterEach': ->
+    squel.cls.globalValueHandlers = @originalHandlers
+  'default': ->
+    handler = -> 'test'
+    squel.registerValueHandler(Date, handler)
+    squel.registerValueHandler(Object, handler)
+    squel.registerValueHandler('boolean', handler)
+
+    assert.same 3, squel.cls.globalValueHandlers.length
+    assert.same { type: Date, handler: handler }, squel.cls.globalValueHandlers[0]
+    assert.same { type: Object, handler: handler }, squel.cls.globalValueHandlers[1]
+    assert.same { type: 'boolean', handler: handler }, squel.cls.globalValueHandlers[2]
+
+  'type should be class constructor': ->
+    assert.throws (-> squel.registerValueHandler 1, null), "type must be a class constructor or string"
+
+  'handler should be function': ->
+    class MyClass
+    assert.throws (-> squel.registerValueHandler MyClass, 1), 'handler must be a function'
+
+  'overrides existing handler': ->
+    handler = -> 'test'
+    handler2 = -> 'test2'
+    squel.registerValueHandler(Date, handler)
+    squel.registerValueHandler(Date, handler2)
+
+    assert.same 1, squel.cls.globalValueHandlers.length
+    assert.same { type: Date, handler: handler2 }, squel.cls.globalValueHandlers[0]
+
+
+test['str()'] = 
+  constructor: ->
+    f = squel.str('GETDATE(?)', 12, 23)
+    assert.ok (f instanceof squel.cls.FunctionBlock)
+    assert.same 'GETDATE(?)', f._strings[0]
+    assert.same [12, 23], f._values[0]
+
+  'custom value handler':
+    beforeEach: ->
+      @inst = squel.str('G(?,?)', 12, 23, 65)
+      
+      handlerConfig = _.find squel.cls.globalValueHandlers, (hc) -> 
+        hc.type is squel.cls.FunctionBlock
+
+      @handler = handlerConfig.handler
+
+    toString: ->
+      assert.same @inst.toString(), @handler(@inst)
+    toParam: ->
+      assert.same @inst.toParam(), @handler(@inst, true)
+
+
+test['Load an SQL flavour'] =
+  beforeEach: ->
+    @flavoursBackup = squel.flavours
+    squel.flavours = {}
+
+  afterEach: ->
+    squel.flavours = @flavoursBackup
+
+  'invalid flavour': ->
+    assert.throws (-> squel.useFlavour 'test'), 'Flavour not available: test'
+
+  'flavour reference should be a function': ->
+    squel.flavours['test'] = 'blah'
+    assert.throws (-> squel.useFlavour 'test'), 'Flavour not available: test'
+
+  'flavour setup function gets executed': ->
+    squel.flavours['test'] = test.mocker.spy()
+    ret = squel.useFlavour 'test'
+    assert.ok squel.flavours['test'].calledOnce
+    assert.ok !!ret.select()
+
+  'can switch flavours': ->
+    squel.flavours['test'] = test.mocker.spy( (s) ->
+      s.cls.dummy = 1
+    )
+    squel.flavours['test2'] = test.mocker.spy( (s) ->
+      s.cls.dummy2 = 2
+    )
+    ret = squel.useFlavour 'test'
+    assert.same ret.cls.dummy, 1
+
+    ret = squel.useFlavour 'test2'
+    assert.same ret.cls.dummy, undefined
+    assert.same ret.cls.dummy2, 2
+
+    ret = squel.useFlavour()
+    assert.same ret.cls.dummy, undefined
+    assert.same ret.cls.dummy2, undefined
+
+  'can get current flavour': ->
+    flavour = 'test'
+    squel.flavours[flavour] = test.mocker.spy()
+
+    ret = squel.useFlavour flavour
+    assert.same ret.flavour, flavour
+
+
+
+test['Builder base class'] =
+  beforeEach: ->
+    @cls = squel.cls.BaseBuilder
+    @inst = new @cls
+
+    @originalHandlers = [].concat(squel.cls.globalValueHandlers)
+
+  afterEach: ->
+    squel.cls.globalValueHandlers = @originalHandlers
+
+  'instanceof Cloneable': ->
+    assert.instanceOf @inst, squel.cls.Cloneable
+
+  'constructor':
+    'default options': ->
+      assert.same squel.cls.DefaultQueryBuilderOptions, @inst.options
+
+    'overridden options': ->
+      @inst = new @cls
+        dummy1: 'str'
+        dummy2: 12.3
+        usingValuePlaceholders: true
+        dummy3: true,
+        globalValueHandlers: [1]
+
+      expectedOptions = _.extend {}, squel.cls.DefaultQueryBuilderOptions,
+        dummy1: 'str'
+        dummy2: 12.3
+        usingValuePlaceholders: true
+        dummy3: true
+        globalValueHandlers: [1]
+
+      assert.same expectedOptions, @inst.options
+
+
+  'registerValueHandler':
+    'afterEach': ->
+      squel.cls.globalValueHandlers = []
+
+    'default': ->
+      handler = -> 'test'
+      @inst.registerValueHandler(Date, handler)
+      @inst.registerValueHandler(Object, handler)
+      @inst.registerValueHandler('number', handler)
+
+      assert.same 3, @inst.options.valueHandlers.length
+      assert.same { type: Date, handler: handler }, @inst.options.valueHandlers[0]
+      assert.same { type: Object, handler: handler }, @inst.options.valueHandlers[1]
+      assert.same { type: 'number', handler: handler }, @inst.options.valueHandlers[2]
+
+    'type should be class constructor': ->
+      assert.throws (=> @inst.registerValueHandler 1, null), "type must be a class constructor or string"
+
+    'handler should be function': ->
+      class MyClass
+      assert.throws (=> @inst.registerValueHandler MyClass, 1), 'handler must be a function'
+
+    'returns instance for chainability': ->
+      handler = -> 'test'
+      assert.same @inst, @inst.registerValueHandler(Date, handler)
+
+    'overrides existing handler': ->
+      handler = -> 'test'
+      handler2 = -> 'test2'
+      @inst.registerValueHandler(Date, handler)
+      @inst.registerValueHandler(Date, handler2)
+
+      assert.same 1, @inst.options.valueHandlers.length
+      assert.same { type: Date, handler: handler2 }, @inst.options.valueHandlers[0]
+
+    'does not touch global value handlers list': ->
+      oldGlobalHandlers = squel.cls.globalValueHandlers
+
+      handler = -> 'test'
+      @inst.registerValueHandler(Date, handler)
+
+      assert.same oldGlobalHandlers, squel.cls.globalValueHandlers
+
+
+  '_sanitizeExpression':
+    'if Expression':
+      'empty expression': ->
+        e = squel.expr()
+        assert.same e, @inst._sanitizeExpression(e)
+      'non-empty expression': ->
+        e = squel.expr().and("s.name <> 'Fred'")
+        assert.same e, @inst._sanitizeExpression(e)
+
+    'if string': ->
+      s = 'BLA BLA'
+      assert.same 'BLA BLA', @inst._sanitizeExpression(s)
+
+    'if neither Expression nor String': ->
+      testFn = => @inst._sanitizeExpression(1)
+      assert.throws testFn, 'expression must be a string or Expression instance'
+
+
+  '_sanitizeName':
+    beforeEach: ->
+      test.mocker.spy @inst, '_sanitizeName'
+
+    'if string': ->
+      assert.same 'bla', @inst._sanitizeName('bla')
+
+    'if boolean': ->
+      assert.throws (=> @inst._sanitizeName(true, 'bla')), 'bla must be a string'
+
+    'if integer': ->
+      assert.throws (=> @inst._sanitizeName(1)), 'undefined must be a string'
+
+    'if float': ->
+      assert.throws (=> @inst._sanitizeName(1.2, 'meh')), 'meh must be a string'
+
+    'if array': ->
+      assert.throws (=> @inst._sanitizeName([1], 'yes')), 'yes must be a string'
+
+    'if object': ->
+      assert.throws (=> @inst._sanitizeName(new Object, 'yes')), 'yes must be a string'
+
+    'if null': ->
+      assert.throws (=> @inst._sanitizeName(null, 'no')), 'no must be a string'
+
+    'if undefined': ->
+      assert.throws (=> @inst._sanitizeName(undefined, 'no')), 'no must be a string'
+
+
+  '_sanitizeField':
+    'default': ->
+      test.mocker.spy @inst, '_sanitizeName'
+
+      assert.same 'abc', @inst._sanitizeField('abc')
+
+      assert.ok @inst._sanitizeName.calledWithExactly 'abc', 'field name'
+
+    'QueryBuilder': ->
+      s = squel.select().from('scores').field('MAX(score)')
+      assert.same s, @inst._sanitizeField(s)
+
+
+  '_sanitizeBaseBuilder':
+    'is not base builder': ->
+      assert.throws (=> @inst._sanitizeBaseBuilder(null)), 'must be a BaseBuilder instance'
+
+    'is a query builder': ->
+      qry = squel.select()
+      assert.same qry, @inst._sanitizeBaseBuilder(qry)
+
+
+  '_sanitizeTable':
+    'default': ->
+      test.mocker.spy @inst, '_sanitizeName'
+
+      assert.same 'abc', @inst._sanitizeTable('abc')
+
+      assert.ok @inst._sanitizeName.calledWithExactly 'abc', 'table'
+
+    'not a string': ->
+      assert.throws (=> @inst._sanitizeTable(null)), 'table name must be a string or a query builder'
+
+    'query builder': ->
+      select = squel.select()
+      assert.same select, @inst._sanitizeTable(select, true)
+
+
+  '_sanitizeFieldAlias': ->
+    'default': ->
+      test.mocker.spy @inst, '_sanitizeName'
+
+      @inst._sanitizeFieldAlias('abc')
+
+      assert.ok @inst._sanitizeName.calledWithExactly 'abc', 'field alias'
+
+
+  '_sanitizeTableAlias': ->
+    'default': ->
+      test.mocker.spy @inst, '_sanitizeName'
+
+      @inst._sanitizeTableAlias('abc')
+
+      assert.ok @inst._sanitizeName.calledWithExactly 'abc', 'table alias'
+
+  '_sanitizeLimitOffset':
+    'undefined': ->
+      assert.throws (=> @inst._sanitizeLimitOffset()), 'limit/offset must be >= 0'
+
+    'null': ->
+      assert.throws (=> @inst._sanitizeLimitOffset null), 'limit/offset must be >= 0'
+
+    'float': ->
+      assert.same 1, @inst._sanitizeLimitOffset 1.2
+
+    'boolean': ->
+      assert.throws (=> @inst._sanitizeLimitOffset false), 'limit/offset must be >= 0'
+
+    'string': ->
+      assert.same 2, @inst._sanitizeLimitOffset '2'
+
+    'array': ->
+      assert.same 3, @inst._sanitizeLimitOffset [3]
+
+    'object': ->
+      assert.throws (=> @inst._sanitizeLimitOffset(new Object)), 'limit/offset must be >= 0'
+
+    'number >= 0': ->
+      assert.same 0, @inst._sanitizeLimitOffset 0
+      assert.same 1, @inst._sanitizeLimitOffset 1
+
+    'number < 0': ->
+      assert.throws (=> @inst._sanitizeLimitOffset(-1)), 'limit/offset must be >= 0'
+
+
+
+  '_sanitizeValue':
+    beforeEach: ->
+      test.mocker.spy @inst, '_sanitizeValue'
+
+    afterEach: ->
+      squel.cls.globalValueHandlers = []
+
+    'if string': ->
+      assert.same 'bla', @inst._sanitizeValue('bla')
+
+    'if boolean': ->
+      assert.same true, @inst._sanitizeValue(true)
+      assert.same false, @inst._sanitizeValue(false)
+
+    'if integer': ->
+      assert.same -1, @inst._sanitizeValue(-1)
+      assert.same 0, @inst._sanitizeValue(0)
+      assert.same 1, @inst._sanitizeValue(1)
+
+    'if float': ->
+      assert.same -1.2, @inst._sanitizeValue(-1.2)
+      assert.same 1.2, @inst._sanitizeValue(1.2)
+
+    'if array': ->
+      assert.throws (=> @inst._sanitizeValue([1])), 'field value must be a string, number, boolean, null or one of the registered custom value types'
+
+    'if object': ->
+      assert.throws (=> @inst._sanitizeValue(new Object)), 'field value must be a string, number, boolean, null or one of the registered custom value types'
+
+    'if null': ->
+      assert.same null, @inst._sanitizeValue(null)
+
+    'if BaseBuilder': ->
+      s = squel.select()
+      assert.same s, @inst._sanitizeValue(s)
+
+    'if undefined': ->
+      assert.throws (=> @inst._sanitizeValue(undefined)), 'field value must be a string, number, boolean, null or one of the registered custom value types'
+
+    'custom handlers':
+      'global': ->
+        squel.registerValueHandler(Date, _.identity)
+        date = new Date
+        assert.same date, @inst._sanitizeValue(date)
+
+      'instance': ->
+        @inst.registerValueHandler(Date, _.identity)
+        date = new Date
+        assert.same date, @inst._sanitizeValue(date)
+
+
+  '_escapeValue': ->
+      @inst.options.replaceSingleQuotes = false
+      assert.same "te'st", @inst._escapeValue("te'st")
+
+      @inst.options.replaceSingleQuotes = true
+      assert.same "te''st", @inst._escapeValue("te'st")
+
+      @inst.options.singleQuoteReplacement = '--'
+      assert.same "te--st", @inst._escapeValue("te'st")
+
+  '_formatTableName':
+    'default': ->
+      assert.same 'abc', @inst._formatTableName('abc')
+
+    'auto quote names':
+      beforeEach: ->
+        @inst.options.autoQuoteTableNames = true
+
+      'default quote character': ->
+        assert.same '`abc`', @inst._formatTableName('abc')
+
+      'custom quote character': ->
+        @inst.options.nameQuoteCharacter = '|'
+        assert.same '|abc|', @inst._formatTableName('abc')
+
+
+  '_formatTableAlias':
+    'default': ->
+      assert.same '`abc`', @inst._formatTableAlias('abc')
+
+    'custom quote character': ->
+      @inst.options.tableAliasQuoteCharacter = '~'
+      assert.same '~abc~', @inst._formatTableAlias('abc')
+
+    'auto quote alias names is OFF': ->
+      @inst.options.autoQuoteAliasNames = false
+      assert.same 'abc', @inst._formatTableAlias('abc')
+
+    'AS is turned ON': ->
+      @inst.options.autoQuoteAliasNames = false
+      @inst.options.useAsForTableAliasNames = true
+      assert.same 'AS abc', @inst._formatTableAlias('abc')
+
+
+
+  '_formatFieldAlias':
+    default: ->
+      assert.same '"abc"', @inst._formatFieldAlias('abc')
+
+    'custom quote character': ->
+      @inst.options.fieldAliasQuoteCharacter = '~'
+      assert.same '~abc~', @inst._formatFieldAlias('abc')
+
+    'auto quote alias names is OFF': ->
+      @inst.options.autoQuoteAliasNames = false
+      assert.same 'abc', @inst._formatFieldAlias('abc')
+
+
+  '_formatFieldName':
+    default: ->
+      assert.same 'abc', @inst._formatFieldName('abc')
+
+    'auto quote names':
+      beforeEach: ->
+        @inst.options.autoQuoteFieldNames = true
+
+      'default quote character': ->
+        assert.same '`abc`.`def`', @inst._formatFieldName('abc.def')
+
+      'do not quote *': ->
+        assert.same '`abc`.*', @inst._formatFieldName('abc.*')
+
+      'custom quote character': ->
+        @inst.options.nameQuoteCharacter = '|'
+        assert.same '|abc|.|def|', @inst._formatFieldName('abc.def')
+
+      'ignore periods when quoting': ->
+        assert.same '`abc.def`', @inst._formatFieldName('abc.def', ignorePeriodsForFieldNameQuotes: true)
+
+
+  '_formatCustomValue':
+    'not a custom value type': ->
+      assert.same null, @inst._formatCustomValue(null)
+      assert.same 'abc', @inst._formatCustomValue('abc')
+      assert.same 12, @inst._formatCustomValue(12)
+      assert.same 1.2, @inst._formatCustomValue(1.2)
+      assert.same true, @inst._formatCustomValue(true)
+      assert.same false, @inst._formatCustomValue(false)
+
+    'custom value type':
+      'global': ->
+        class MyClass
+        myObj = new MyClass
+
+        squel.registerValueHandler MyClass, () -> 3.14
+        squel.registerValueHandler 'boolean', (v) -> 'a' + v
+
+        assert.same 3.14, @inst._formatCustomValue(myObj)
+        assert.same 'atrue', @inst._formatCustomValue(true)
+
+      'instance': ->
+        class MyClass
+        myObj = new MyClass
+
+        @inst.registerValueHandler MyClass, () -> 3.14
+        @inst.registerValueHandler 'number', (v) -> v + 'a'
+
+        assert.same 3.14, @inst._formatCustomValue(myObj)
+        assert.same '5.2a', @inst._formatCustomValue(5.2)
+
+      'instance handler takes precedence over global': ->
+        @inst.registerValueHandler Date, (d) -> 'hello'
+        squel.registerValueHandler Date, (d) -> 'goodbye'
+
+        assert.same "hello", @inst._formatCustomValue(new Date)
+
+        @inst = new @cls
+          valueHandlers: []
+        assert.same "goodbye", @inst._formatCustomValue(new Date)
+
+      'whether to format for parameterized output': ->
+        @inst.registerValueHandler Date, (d, asParam) ->
+          return if asParam then 'foo' else 'bar'
+
+        val = new Date()
+
+        assert.same 'foo', @inst._formatCustomValue(val, true)
+        assert.same 'bar', @inst._formatCustomValue(val)
+        
+
+  '_formatValueForParamArray':
+    'Query builder': ->
+      s = squel.select().from('table')
+      assert.same s, @inst._formatValueForParamArray(s)
+
+    'else calls _formatCustomValue': ->
+      spy = test.mocker.stub @inst, '_formatCustomValue', (v, asParam) -> 
+        'test' + (if asParam then 'foo' else 'bar')
+
+      assert.same 'testfoo', @inst._formatValueForParamArray(null)
+      assert.same 'testfoo', @inst._formatValueForParamArray('abc')
+      assert.same 'testfoo', @inst._formatValueForParamArray(12)
+      assert.same 'testfoo', @inst._formatValueForParamArray(1.2)
+      assert.same 'testfoo', @inst._formatValueForParamArray(true)
+      assert.same 'testfoo', @inst._formatValueForParamArray(false)
+
+      assert.same 6, spy.callCount
+
+    'Array - recursively calls itself on each element': ->
+      spy = test.mocker.spy @inst, '_formatValueForParamArray'
+
+      v = [ squel.select().from('table'), 1.2 ]
+      res = @inst._formatValueForParamArray(v)
+
+      assert.same v, res
+
+      assert.same 3, spy.callCount
+      assert.ok spy.calledWith v[0]
+      assert.ok spy.calledWith v[1]
+
+
+
+  '_formatValueForQueryString':
+    'null': ->
+      assert.same 'NULL', @inst._formatValueForQueryString(null)
+
+    'boolean': ->
+      assert.same 'TRUE', @inst._formatValueForQueryString(true)
+      assert.same 'FALSE', @inst._formatValueForQueryString(false)
+
+    'integer': ->
+      assert.same 12, @inst._formatValueForQueryString(12)
+
+    'float': ->
+      assert.same 1.2, @inst._formatValueForQueryString(1.2)
+
+    'string':
+      'have string formatter function': ->
+        @inst.options.stringFormatter = (str) -> "N(#{str})"
+
+        assert.same "N(test)", @inst._formatValueForQueryString('test')
+
+      'default': ->
+        escapedValue = undefined
+        test.mocker.stub @inst, '_escapeValue', (str) -> escapedValue or str
+
+        assert.same "'test'", @inst._formatValueForQueryString('test')
+
+        assert.same "'test'", @inst._formatValueForQueryString('test')
+        assert.ok @inst._escapeValue.calledWithExactly('test')
+        escapedValue = 'blah'
+        assert.same "'blah'", @inst._formatValueForQueryString('test')
+
+      'dont quote': ->
+        escapedValue = undefined
+        test.mocker.stub @inst, '_escapeValue', (str) -> escapedValue or str
+
+        assert.same "test", @inst._formatValueForQueryString('test', dontQuote: true )
+
+        assert.ok @inst._escapeValue.notCalled
+
+    'Array - recursively calls itself on each element': ->
+      spy = test.mocker.spy @inst, '_formatValueForQueryString'
+
+      expected = "('test', 123, TRUE, 1.2, NULL)"
+      assert.same expected, @inst._formatValueForQueryString([ 'test', 123, true, 1.2, null ])
+
+      assert.same 6, spy.callCount
+      assert.ok spy.calledWith 'test'
+      assert.ok spy.calledWith 123
+      assert.ok spy.calledWith true
+      assert.ok spy.calledWith 1.2
+      assert.ok spy.calledWith null
+
+    'BaseBuilder': ->
+      spy = test.mocker.stub @inst, '_applyNestingFormatting', (v) => "{{#{v}}}"
+      s = squel.select().from('table')
+      assert.same '{{SELECT * FROM table}}', @inst._formatValueForQueryString(s)
+
+    'checks to see if it is custom value type first': ->
+      test.mocker.stub @inst, '_formatCustomValue', (val, asParam) -> 
+        12 + (if asParam then 25 else 65)
+      test.mocker.stub @inst, '_applyNestingFormatting', (v) -> "{#{v}}"
+      assert.same '{77}', @inst._formatValueForQueryString(123)
+
+
+  '_applyNestingFormatting':
+    default: ->
+      assert.same '(77)', @inst._applyNestingFormatting('77')
+      assert.same '((77)', @inst._applyNestingFormatting('(77')
+      assert.same '(77))', @inst._applyNestingFormatting('77)')
+      assert.same '(77)', @inst._applyNestingFormatting('(77)')
+    'no nesting': ->
+      assert.same '77', @inst._applyNestingFormatting('77', false)
+
+
+  '_buildString':
+    'empty': ->
+      assert.same @inst._buildString('', []), {
+        text: '',
+        values: [],
+      }
+    'no params':
+      'non-parameterized': ->
+        assert.same @inst._buildString('abc = 3', []), {
+          text: 'abc = 3',
+          values: []
+        }
+      'parameterized': ->
+        assert.same @inst._buildString('abc = 3', [], { buildParameterized: true }), {
+          text: 'abc = 3',
+          values: []
+        }
+    'non-array':
+      'non-parameterized': ->
+        assert.same @inst._buildString('a = ? ? ? ?', [2, 'abc', false, null]), {
+          text: 'a = 2 \'abc\' FALSE NULL',
+          values: []
+        }
+      'parameterized': ->
+        assert.same @inst._buildString('a = ? ? ? ?', [2, 'abc', false, null], { buildParameterized: true }), {
+          text: 'a = ? ? ? ?',
+          values: [2, 'abc', false, null]
+        }
+    'array': ->
+      'non-parameterized': ->
+        assert.same @inst._buildString('a = ?', [[1,2,3]]), {
+          text: 'a = (1, 2, 3)',
+          values: [],
+        }
+      'parameterized': ->
+        assert.same @inst._buildString('a = ?', [[1,2,3]], { buildParameterized: true }), {
+          text: 'a = (?, ?, ?)',
+          values: [1, 2, 3]
+        }
+    'nested builder': ->
+      beforeEach:
+        @s = squel.select().from('master').where('b = ?', 5)
+      'non-parameterized': ->
+        assert.same @inst._buildString('a = ?', [@s]), {
+          text: 'a = (SELECT * FROM master WHERE (b = ?))',
+          values: [5]
+        }
+      'parameterized': ->
+        assert.same @inst._buildString('a = ?', [@s], { buildParameterized: true }), {
+          text: 'a = (SELECT * FROM master WHERE (b = ?))',
+          values: [5]
+        }
+    'return nested output':
+      'non-parameterized': ->
+        assert.same @inst._buildString('a = ?', [3], { nested: true }), {
+          text: '(a = 3)',
+          values: []
+        }
+      'parameterized': ->
+        assert.same @inst._buildString('a = ?', [3], { buildParameterized: true, nested: true }), {
+          text: '(a = ?)',
+          values: [3]
+        }
+    'string formatting options': ->
+      options = 
+        formattingOptions:
+          dontQuote: true
+
+      assert.same @inst._buildString('a = ?', ['NOW()'], options), {
+        text: 'a = NOW()',
+        values: []
+      }
+    'custom parameter character': ->
+      beforeEach: ->
+        @inst.options.parameterCharacter = '@@'
+
+      'non-parameterized': ->
+        assert.same @inst._buildString('a = @@', [[1,2,3]]), {
+          text: 'a = (1, 2, 3)',
+          values: [],
+        }
+      'parameterized': ->
+        assert.same @inst._buildString('a = @@', [[1,2,3]]), {
+          text: 'a = (@@, @@, @@)',
+          values: [1,2,3],
+        }
+
+  '_buildManyStrings':
+    'empty': ->
+      assert.same @inst._buildManyStrings([], []), {
+        text: '',
+        values: [],
+      }
+    'simple':
+      beforeEach: ->
+        @strings = [
+          'a = ?',
+          'b IN ? AND c = ?'
+        ]
+
+        @values = [
+          ['elephant'],
+          [[1,2,3], 4]
+        ]
+
+      'non-parameterized': ->
+        assert.same @inst._buildManyStrings(@strings, @values), {
+          text: 'a = \'elephant\' b IN (1, 2, 3) AND c = 4',
+          values: [],
+        }
+      'parameterized': ->
+        assert.same @inst._buildManyStrings(@strings, @values, { buildParameterized: true }), {
+          text: 'a = ? b IN (?, ?, ?) AND c = ?',
+          values: ['elephant', 1, 2, 3, 4],
+        }
+
+    'return nested': ->
+      'non-parameterized': ->
+        assert.same @inst._buildManyStrings(['a = ?', 'b = ?'], [[1], [2]], { nested: true }), {
+          text: '(a = 1 b = 2)',
+          values: [],
+        }
+      'parameterized': ->
+        assert.same @inst._buildManyStrings(['a = ?', 'b = ?'], [[1], [2]], { buildParameterized: true, nested: true }), {
+          text: '(a = ? b = ?)',
+          values: [1, 2],
+        }
+
+    'custom separator': ->
+      'non-parameterized': ->
+        @inst.options.separator = '|'
+        assert.same @inst._buildManyStrings(['a = ?', 'b = ?'], [[1], [2]]), {
+          text: '(a = 1|b = 2)',
+          values: [],
+        }
+      'parameterized': ->
+        assert.same @inst._buildManyStrings(['a = ?', 'b = ?'], [[1], [2]], { buildParameterized: true}), {
+          text: '(a = ?|b = ?)',
+          values: [1, 2],
+        }
+
+  'toParam': ->
+    spy = test.mocker.stub @inst, '_toParamString', ->
+      {
+        text: 'dummy'
+        values: [1]
+      }
+
+    options = {test: 2}
+    assert.same @inst.toParam(options), {
+      text: 'dummy'
+      values: [1]      
+    }
+
+    spy.should.have.been.calledOnce
+    assert.same spy.getCall(0).args[0].test, 2
+    assert.same spy.getCall(0).args[0].buildParameterized, true
+
+  'toString': ->
+    spy = test.mocker.stub @inst, '_toParamString', ->
+      {
+        text: 'dummy'
+        values: [1]
+      }
+
+    options = {test: 2}
+    assert.same @inst.toString(options), 'dummy'
+
+    spy.should.have.been.calledOnce
+    assert.same spy.getCall(0).args[0], options
+
+
+test['QueryBuilder base class'] =
+  beforeEach: ->
+    @cls = squel.cls.QueryBuilder
+    @inst = new @cls
+
+  'instanceof base builder': ->
+    assert.instanceOf @inst, squel.cls.BaseBuilder
+
+  'constructor':
+    'default options': ->
+      assert.same squel.cls.DefaultQueryBuilderOptions, @inst.options
+
+    'overridden options': ->
+      @inst = new @cls
+        dummy1: 'str'
+        dummy2: 12.3
+        usingValuePlaceholders: true
+        dummy3: true
+
+      expectedOptions = _.extend {}, squel.cls.DefaultQueryBuilderOptions,
+        dummy1: 'str'
+        dummy2: 12.3
+        usingValuePlaceholders: true
+        dummy3: true
+
+      assert.same expectedOptions, @inst.options
+
+    'default blocks - none': ->
+      assert.same [], @inst.blocks
+
+    'blocks passed in':
+      'exposes block methods': ->
+        limitExposedMethodsSpy = test.mocker.spy(squel.cls.LimitBlock.prototype, 'exposedMethods');
+        distinctExposedMethodsSpy = test.mocker.spy(squel.cls.DistinctBlock.prototype, 'exposedMethods');
+        limitSpy = test.mocker.spy(squel.cls.LimitBlock.prototype, 'limit')
+        distinctSpy = test.mocker.spy(squel.cls.DistinctBlock.prototype, 'distinct')
+
+        blocks = [
+          new squel.cls.LimitBlock(),
+          new squel.cls.DistinctBlock()
+        ]
+
+        @inst = new @cls({}, blocks)
+
+        assert.ok limitExposedMethodsSpy.calledOnce
+        assert.ok distinctExposedMethodsSpy.calledOnce
+
+        assert.typeOf @inst.distinct, 'function'
+        assert.typeOf @inst.limit, 'function'
+
+        assert.same @inst, @inst.limit(2)
+        assert.ok limitSpy.calledOnce
+        assert.ok limitSpy.calledOn(blocks[0])
+
+        assert.same @inst, @inst.distinct()
+        assert.ok distinctSpy.calledOnce
+        assert.ok distinctSpy.calledOn(blocks[1])
+
+
+      'cannot expose the same method twice': ->
+        blocks = [
+          new squel.cls.DistinctBlock(),
+          new squel.cls.DistinctBlock()
+        ]
+
+        try
+          @inst = new @cls({}, blocks)
+          throw new Error 'should not reach here'
+        catch err
+          assert.same 'Error: Builder already has a builder method called: distinct', err.toString()
+
+
+  'updateOptions()':
+    'updates query builder options': ->
+      oldOptions = _.extend({}, @inst.options)
+
+      @inst.updateOptions
+        updated: false
+
+      expected = _.extend oldOptions,
+        updated: false
+
+      assert.same expected, @inst.options
+
+    'updates building block options': ->
+      @inst.blocks = [
+        new squel.cls.Block()
+      ]
+      oldOptions = _.extend({}, @inst.blocks[0].options)
+
+      @inst.updateOptions
+        updated: false
+
+      expected = _.extend oldOptions,
+        updated: false
+
+      assert.same expected, @inst.blocks[0].options
+
+
+
+  'toString()':
+    'returns empty if no blocks': ->
+      assert.same '', @inst.toString()
+
+    'skips empty block strings': ->
+      @inst.blocks = [
+        new squel.cls.StringBlock({}, ''),
+      ]
+
+      assert.same '', @inst.toString()
+
+    'returns final query string': ->
+      i = 1
+      toStringSpy = test.mocker.stub squel.cls.StringBlock.prototype, '_toParamString', -> 
+        {
+          text: "ret#{++i}"
+          values: []
+        }
+
+      @inst.blocks = [
+        new squel.cls.StringBlock({}, 'STR1'),
+        new squel.cls.StringBlock({}, 'STR2'),
+        new squel.cls.StringBlock({}, 'STR3')
+      ]
+
+      assert.same 'ret2 ret3 ret4', @inst.toString()
+
+      assert.ok toStringSpy.calledThrice
+      assert.ok toStringSpy.calledOn(@inst.blocks[0])
+      assert.ok toStringSpy.calledOn(@inst.blocks[1])
+      assert.ok toStringSpy.calledOn(@inst.blocks[2])
+
+
+  'toParam()':
+    'returns empty if no blocks': ->
+      assert.same { text: '', values: [] }, @inst.toParam()
+
+    'skips empty block strings': ->
+      @inst.blocks = [
+        new squel.cls.StringBlock({}, ''),
+      ]
+
+      assert.same { text: '', values: [] }, @inst.toParam()
+
+    'returns final query string': ->
+      @inst.blocks = [
+        new squel.cls.StringBlock({}, 'STR1'),
+        new squel.cls.StringBlock({}, 'STR2'),
+        new squel.cls.StringBlock({}, 'STR3')
+      ]
+
+      i = 1
+      toStringSpy = test.mocker.stub squel.cls.StringBlock.prototype, '_toParamString', -> 
+        {
+          text: "ret#{++i}"
+          values: []
+        }
+
+      assert.same { text: 'ret2 ret3 ret4', values: [] }, @inst.toParam()
+
+      assert.ok toStringSpy.calledThrice
+      assert.ok toStringSpy.calledOn(@inst.blocks[0])
+      assert.ok toStringSpy.calledOn(@inst.blocks[1])
+      assert.ok toStringSpy.calledOn(@inst.blocks[2])
+
+    'returns query with unnumbered parameters': ->
+      @inst.blocks = [
+        new squel.cls.WhereBlock({}),
+      ]
+
+      @inst.blocks[0]._toParamString = test.mocker.spy -> { 
+        text: 'a = ? AND b in (?, ?)',
+        values: [1, 2, 3]
+      }
+
+      assert.same { text: 'a = ? AND b in (?, ?)', values: [1, 2, 3]}, @inst.toParam()
+
+    'returns query with numbered parameters': ->
+      @inst = new @cls
+        numberedParameters: true
+
+      @inst.blocks = [
+        new squel.cls.WhereBlock({}),
+      ]
+
+      test.mocker.stub squel.cls.WhereBlock.prototype, '_toParamString', -> { 
+        text: 'a = ? AND b in (?, ?)', values: [1, 2, 3]
+      }
+
+      assert.same @inst.toParam(), { text: 'a = $1 AND b in ($2, $3)', values: [1, 2, 3]}
+
+    'returns query with numbered parameters and custom prefix': ->
+      @inst = new @cls
+        numberedParameters: true
+        numberedParametersPrefix: '&%'
+
+      @inst.blocks = [
+        new squel.cls.WhereBlock({}),
+      ]
+
+      test.mocker.stub squel.cls.WhereBlock.prototype, '_toParamString', -> { 
+        text: 'a = ? AND b in (?, ?)', values: [1, 2, 3]
+      } 
+
+      assert.same @inst.toParam(), { text: 'a = &%1 AND b in (&%2, &%3)', values: [1, 2, 3]}
+
+
+  'cloning':
+    'blocks get cloned properly': ->
+      blockCloneSpy = test.mocker.spy(squel.cls.StringBlock.prototype, 'clone')
+
+      @inst.blocks = [
+        new squel.cls.StringBlock({}, 'TEST')
+      ]
+
+      newinst = @inst.clone()
+      @inst.blocks[0].str = 'TEST2'
+
+      assert.same 'TEST', newinst.blocks[0].toString()
+
+  'registerValueHandler':
+    'beforEach': ->
+      @originalHandlers = [].concat(squel.cls.globalValueHandlers)
+    'afterEach': ->
+      squel.cls.globalValueHandlers = @originalHandlers
+
+    'calls through to base class method': ->
+      baseBuilderSpy = test.mocker.spy(squel.cls.BaseBuilder.prototype, 'registerValueHandler')
+
+      handler = -> 'test'
+      @inst.registerValueHandler(Date, handler)
+      @inst.registerValueHandler('number', handler)
+
+      assert.ok baseBuilderSpy.calledTwice
+      assert.ok baseBuilderSpy.calledOn(@inst)
+
+    'returns instance for chainability': ->
+      handler = -> 'test'
+      assert.same @inst, @inst.registerValueHandler(Date, handler)
+
+    'calls through to blocks': ->
+      @inst.blocks = [
+        new squel.cls.StringBlock({}, ''),
+      ]
+
+      baseBuilderSpy = test.mocker.spy(@inst.blocks[0], 'registerValueHandler')
+
+      handler = -> 'test'
+      @inst.registerValueHandler(Date, handler)
+
+      assert.ok baseBuilderSpy.calledOnce
+      assert.ok baseBuilderSpy.calledOn(@inst.blocks[0])
+
+  'get block':
+    'valid': ->
+      block = new squel.cls.FunctionBlock()
+      @inst.blocks.push(block)
+      assert.same block, @inst.getBlock(squel.cls.FunctionBlock)
+    'invalid': ->
+      assert.throws (-> @inst.getBlock(squel.cls.FunctionBlock) )
+
+
+
+
+
+module?.exports[require('path').basename(__filename)] = test

+ 1457 - 0
www/lib/squel/test/blocks.test.coffee

@@ -0,0 +1,1457 @@
+###
+Copyright (c) 2014 Ramesh Nair (hiddentao.com)
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+###
+
+
+squel = require "../dist/squel-basic"
+{_, testCreator, assert, expect, should} = require './testbase'
+test = testCreator()
+
+
+
+test['Blocks'] =
+  'Block base class':
+    beforeEach: ->
+      @inst = new squel.cls.Block()
+
+    'instanceof of BaseBuilder': ->
+      assert.instanceOf @inst, squel.cls.BaseBuilder
+
+    'options': ->
+      expectedOptions = _.extend {}, squel.cls.DefaultQueryBuilderOptions,
+        usingValuePlaceholders: true
+        dummy: true
+
+      @inst = new squel.cls.Block
+        usingValuePlaceholders: true
+        dummy: true
+
+      assert.same expectedOptions, @inst.options
+
+    '_toParamString()': ->
+      assert.throws (=> @inst.toString()), 'Not yet implemented'
+
+    'exposedMethods()':
+      'returns methods': ->
+        @inst['method1'] = -> return false
+        @inst['method2'] = -> return false
+
+        assert.ok ['method1', 'method2'], (name for name of @inst.exposedMethods())
+
+      'ignores methods prefixed with _': ->
+        @inst['_method'] = -> return false
+
+        assert.ok undefined is _.find (name for name of @inst.exposedMethods()), (name) ->
+          return name is '_method'
+
+      'ignores toString()': ->
+        assert.ok undefined is _.find (name for name of @inst.exposedMethods()), (name) ->
+          return name is 'toString'
+
+    'cloning copies the options over': ->
+      @inst.options.dummy = true;
+
+      newinst = @inst.clone()
+
+      @inst.options.dummy = false;
+
+      assert.same true, newinst.options.dummy
+
+
+
+  'StringBlock':
+    beforeEach: ->
+      @cls = squel.cls.StringBlock
+      @inst = new @cls
+
+    'instanceof of Block': ->
+      assert.instanceOf @inst, squel.cls.Block
+
+    '_toParamString()':
+      'non-parameterized': ->
+        @inst = new @cls {}, 'TAG'
+
+        assert.same @inst._toParamString(), { 
+          text: 'TAG'
+          values: [] 
+        }
+      'parameterized': ->
+        @inst = new @cls {}, 'TAG'
+
+        assert.same @inst._toParamString(buildParameterized: true), { 
+          text: 'TAG'
+          values: [] 
+        }
+
+
+
+  'FunctionBlock':
+    beforeEach: ->
+      @cls = squel.cls.FunctionBlock
+      @inst = new @cls
+
+    'instanceof of Block': ->
+      assert.instanceOf @inst, squel.cls.Block
+
+    'initial member values': ->
+      assert.same [], @inst._values
+      assert.same [], @inst._strings
+
+    '_toParamString()':
+      'when not set': ->
+        assert.same @inst._toParamString(), { 
+          text: ''
+          values: [] 
+        } 
+      'non-parameterized': ->
+        @inst.function('bla')
+        @inst.function('bla2')
+
+        assert.same @inst._toParamString(), {
+          text: 'bla bla2', 
+          values: []
+        }
+      'parameterized': ->
+        @inst.function('bla ?', 2)
+        @inst.function('bla2 ?', 3)
+
+        assert.same @inst._toParamString(buildParameterized: true), {
+          text: 'bla ? bla2 ?', 
+          values: [2, 3]
+        }
+
+
+  'AbstractTableBlock':
+    beforeEach: ->
+      @cls = squel.cls.AbstractTableBlock
+      @inst = new @cls()
+
+    'instanceof of Block': ->
+      assert.instanceOf @inst, squel.cls.Block
+
+    'initial field values': ->
+      assert.same [], @inst._tables
+
+    'has table':
+      'no': ->
+        assert.same false, @inst._hasTable()
+      'yes': ->
+        @inst._table('blah')
+        assert.same true, @inst._hasTable()
+
+    '_table()':
+      'saves inputs': ->
+        @inst._table('table1')
+        @inst._table('table2', 'alias2')
+        @inst._table('table3')
+
+        expectedFroms = [
+          {
+          table: 'table1',
+          alias: null
+          },
+          {
+          table: 'table2',
+          alias: 'alias2'
+          },
+          {
+          table: 'table3',
+          alias: null
+          }
+        ]
+
+        assert.same expectedFroms, @inst._tables
+
+      'sanitizes inputs': ->
+        sanitizeTableSpy = test.mocker.stub @cls.prototype, '_sanitizeTable', -> return '_t'
+        sanitizeAliasSpy = test.mocker.stub @cls.prototype, '_sanitizeTableAlias', -> return '_a'
+
+        @inst._table('table', 'alias')
+
+        assert.ok sanitizeTableSpy.calledWith 'table'
+        assert.ok sanitizeAliasSpy.calledWithExactly 'alias'
+
+        assert.same [ { table: '_t', alias: '_a' }], @inst._tables
+
+
+      'handles single-table mode': ->
+        @inst.options.singleTable = true
+
+        @inst._table('table1')
+        @inst._table('table2')
+        @inst._table('table3')
+
+        expected = [
+          {
+          table: 'table3',
+          alias: null
+          }
+        ]
+
+        assert.same expected, @inst._tables
+
+      'builder as table': ->
+        sanitizeTableSpy = test.mocker.spy @cls.prototype, '_sanitizeTable'
+
+        innerTable1 = squel.select()
+        innerTable2 = squel.select()
+
+        @inst._table(innerTable1)
+        @inst._table(innerTable2, 'Inner2')
+
+        assert.ok sanitizeTableSpy.calledWithExactly innerTable1
+        assert.ok sanitizeTableSpy.calledWithExactly innerTable2
+
+        expected = [
+          {
+          alias: null
+          table: innerTable1
+          }
+          {
+          alias: 'Inner2'
+          table: innerTable2
+          }
+        ]
+
+        assert.same expected, @inst._tables
+
+    '_toParamString()':
+      beforeEach: ->
+        @innerTable1 = squel.select().from('inner1').where('a = ?', 3)
+
+      'no table': ->
+        assert.same @inst._toParamString(), {
+          text: ''
+          values: []
+        }
+
+      'prefix': ->
+        @inst.options.prefix = 'TEST'
+
+        @inst._table('table2', 'alias2')
+
+        assert.same @inst._toParamString(), {
+          text: 'TEST table2 `alias2`',
+          values: []
+        }
+
+
+      'non-parameterized': ->
+        @inst._table(@innerTable1)
+        @inst._table('table2', 'alias2')
+        @inst._table('table3')
+
+        assert.same @inst._toParamString(), {
+          text: '(SELECT * FROM inner1 WHERE (a = 3)), table2 `alias2`, table3'
+          values: []
+        }
+      'parameterized': ->
+        @inst._table(@innerTable1)
+        @inst._table('table2', 'alias2')
+        @inst._table('table3')
+
+        assert.same @inst._toParamString(buildParameterized: true), {
+          text: '(SELECT * FROM inner1 WHERE (a = ?)), table2 `alias2`, table3'
+          values: [3]
+        }
+
+
+
+  'FromTableBlock':
+    beforeEach: ->
+      @cls = squel.cls.FromTableBlock
+      @inst = new @cls()
+
+    'check prefix': ->
+      assert.same @inst.options.prefix, 'FROM'
+
+    'instanceof of AbstractTableBlock': ->
+      assert.instanceOf @inst, squel.cls.AbstractTableBlock
+
+    'from()':
+      'calls base class handler': ->
+        baseMethodSpy = test.mocker.stub squel.cls.AbstractTableBlock.prototype, '_table'
+
+        @inst.from('table1')
+        @inst.from('table2', 'alias2')
+
+        assert.same 2, baseMethodSpy.callCount
+        assert.ok baseMethodSpy.calledWithExactly('table1', null)
+        assert.ok baseMethodSpy.calledWithExactly('table2', 'alias2')
+
+
+
+  'UpdateTableBlock':
+    beforeEach: ->
+      @cls = squel.cls.UpdateTableBlock
+      @inst = new @cls()
+
+    'instanceof of AbstractTableBlock': ->
+      assert.instanceOf @inst, squel.cls.AbstractTableBlock
+
+    'check prefix': ->
+      assert.same @inst.options.prefix, undefined
+
+    'table()':
+      'calls base class handler': ->
+        baseMethodSpy = test.mocker.stub squel.cls.AbstractTableBlock.prototype, '_table'
+
+        @inst.table('table1')
+        @inst.table('table2', 'alias2')
+
+        assert.same 2, baseMethodSpy.callCount
+        assert.ok baseMethodSpy.calledWithExactly('table1', null)
+        assert.ok baseMethodSpy.calledWithExactly('table2', 'alias2')
+
+
+
+  'TargetTableBlock':
+    beforeEach: ->
+      @cls = squel.cls.TargetTableBlock
+      @inst = new @cls()
+
+    'instanceof of AbstractTableBlock': ->
+      assert.instanceOf @inst, squel.cls.AbstractTableBlock
+
+    'check prefix': ->
+      assert.same @inst.options.prefix, undefined
+
+    'table()':
+      'calls base class handler': ->
+        baseMethodSpy = test.mocker.stub squel.cls.AbstractTableBlock.prototype, '_table'
+
+        @inst.target('table1')
+        @inst.target('table2')
+
+        assert.same 2, baseMethodSpy.callCount
+        assert.ok baseMethodSpy.calledWithExactly('table1')
+        assert.ok baseMethodSpy.calledWithExactly('table2')
+
+
+
+
+  'IntoTableBlock':
+    beforeEach: ->
+      @cls = squel.cls.IntoTableBlock
+      @inst = new @cls()
+
+    'instanceof of AbstractTableBlock': ->
+      assert.instanceOf @inst, squel.cls.AbstractTableBlock
+
+    'check prefix': ->
+      assert.same @inst.options.prefix, 'INTO'      
+
+    'single table': ->
+      assert.ok @inst.options.singleTable
+
+    'into()':
+      'calls base class handler': ->
+        baseMethodSpy = test.mocker.stub squel.cls.AbstractTableBlock.prototype, '_table'
+
+        @inst.into('table1')
+        @inst.into('table2')
+
+        assert.same 2, baseMethodSpy.callCount
+        assert.ok baseMethodSpy.calledWith('table1')
+        assert.ok baseMethodSpy.calledWith('table2')
+
+    '_toParamString()':
+      'requires table to have been provided': ->
+        try
+          @inst._toParamString()
+          throw new Error 'should not reach here'
+        catch err
+          assert.same 'Error: into() needs to be called', err.toString()
+
+
+
+
+  'GetFieldBlock':
+    beforeEach: ->
+      @cls = squel.cls.GetFieldBlock
+      @inst = new @cls()
+
+    'instanceof of Block': ->
+      assert.instanceOf @inst, squel.cls.Block
+
+    'fields() - object':
+      'saves inputs': ->
+        fieldSpy = test.mocker.spy(@inst, 'field')
+
+        @inst.fields({
+          'field1': null
+          'field2': 'alias2'
+          'field3': null
+        }, { dummy: true})
+
+        expected = [
+          {
+            name: 'field1',
+            alias: null,
+            options: {
+              dummy: true
+            }
+          },
+          {
+            name: 'field2',
+            alias: 'alias2'
+            options: {
+              dummy: true
+            }
+          },
+          {
+            name: 'field3',
+            alias: null
+            options: {
+              dummy: true
+            }
+          }
+        ]
+
+        assert.ok fieldSpy.calledThrice
+        assert.ok fieldSpy.calledWithExactly('field1', null, dummy: true)
+        assert.ok fieldSpy.calledWithExactly('field2', 'alias2', dummy: true)
+        assert.ok fieldSpy.calledWithExactly('field3', null, dummy: true)
+
+        assert.same expected, @inst._fields
+
+    'fields() - array':
+      'saves inputs': ->
+        fieldSpy = test.mocker.spy(@inst, 'field')
+
+        @inst.fields([ 'field1', 'field2', 'field3' ], { dummy: true})
+
+        expected = [
+          {
+            name: 'field1',
+            alias: null
+            options: {
+              dummy: true
+            }
+          },
+          {
+            name: 'field2',
+            alias: null
+            options: {
+              dummy: true
+            }
+          },
+          {
+            name: 'field3',
+            alias: null
+            options: {
+              dummy: true
+            }
+          }
+        ]
+
+        assert.ok fieldSpy.calledThrice
+        assert.ok fieldSpy.calledWithExactly('field1', null, dummy: true)
+        assert.ok fieldSpy.calledWithExactly('field2', null, dummy: true)
+        assert.ok fieldSpy.calledWithExactly('field3', null, dummy: true)
+
+        assert.same expected, @inst._fields
+
+    'field()':
+      'saves inputs': ->
+        @inst.field('field1')
+        @inst.field('field2', 'alias2')
+        @inst.field('field3')
+
+        expected = [
+          {
+          name: 'field1',
+          alias: null,
+          options: {},
+          },
+          {
+          name: 'field2',
+          alias: 'alias2'
+          options: {},
+          },
+          {
+          name: 'field3',
+          alias: null
+          options: {},
+          }
+        ]
+
+        assert.same expected, @inst._fields
+
+    'field() - discard duplicates':
+      'saves inputs': ->
+        @inst.field('field1')
+        @inst.field('field2', 'alias2')
+        @inst.field('field2', 'alias2')
+        @inst.field('field1', 'alias1')
+
+        expected = [
+          {
+          name: 'field1',
+          alias: null
+          options: {},
+          },
+          {
+          name: 'field2',
+          alias: 'alias2'
+          options: {},
+          },
+          {
+          name: 'field1',
+          alias: 'alias1'
+          options: {},
+          }
+        ]
+
+        assert.same expected, @inst._fields
+
+      'sanitizes inputs': ->
+        sanitizeFieldSpy = test.mocker.stub @cls.prototype, '_sanitizeField', -> return '_f'
+        sanitizeAliasSpy = test.mocker.stub @cls.prototype, '_sanitizeFieldAlias', -> return '_a'
+
+        @inst.field('field1', 'alias1', { dummy: true})
+
+        assert.ok sanitizeFieldSpy.calledWithExactly 'field1'
+        assert.ok sanitizeAliasSpy.calledWithExactly 'alias1'
+
+        assert.same @inst._fields, [ { 
+          name: '_f', 
+          alias: '_a' 
+          options:
+            dummy: true
+        } ]
+
+    '_toParamString()':
+      beforeEach: ->
+        @queryBuilder = squel.select()
+        @fromTableBlock = @queryBuilder.getBlock(squel.cls.FromTableBlock)
+
+      'returns all fields when none provided and table is set': ->
+        @fromTableBlock._hasTable = -> true
+
+        assert.same @inst._toParamString(queryBuilder: @queryBuilder), {
+          text: '*', 
+          values: []
+        }
+
+      'but returns nothing if no table set': ->
+        @fromTableBlock._hasTable = -> false
+
+        assert.same @inst._toParamString(queryBuilder: @queryBuilder), {
+          text: ''
+          values: []
+        }
+
+      'returns formatted query phrase': ->
+        beforeEach: ->
+          @fromTableBlock._hasTable = -> true
+          @inst.field(squel.str('GETDATE(?)', 3), 'alias1')
+          @inst.field('field2', 'alias2', { dummy: true })
+          @inst.field('field3')
+        'non-parameterized': ->
+          assert.same @inst._toParamString(queryBuilder: @queryBuilder), {
+            text: '(GETDATE(3)) AS "alias1", field2 AS "alias2", field3'
+            values: []
+          }
+        'parameterized': ->
+          assert.same @inst._toParamString(queryBuilder: @queryBuilder, buildParameterized: true), {
+            text: '(GETDATE(?)) AS "alias1", field2 AS "alias2", field3'
+            values: [3]
+          }
+
+
+
+  'AbstractSetFieldBlock':
+    beforeEach: ->
+      @cls = squel.cls.AbstractSetFieldBlock
+      @inst = new @cls()
+
+    'instanceof of Block': ->
+      assert.instanceOf @inst, squel.cls.Block
+
+    '_set()':
+      'saves inputs': ->
+        @inst._set('field1', 'value1', dummy: 1)
+        @inst._set('field2', 'value2', dummy: 2)
+        @inst._set('field3', 'value3', dummy: 3)
+        @inst._set('field4')
+
+        expectedFields = [ 'field1', 'field2', 'field3', 'field4' ]
+        expectedValues = [ [ 'value1', 'value2', 'value3', undefined ] ]
+        expectedFieldOptions = [ [ {dummy: 1}, {dummy: 2}, {dummy: 3}, {} ] ]
+
+        assert.same expectedFields, @inst._fields
+        assert.same expectedValues, @inst._values
+        assert.same expectedFieldOptions, @inst._valueOptions
+
+      'sanitizes inputs': ->
+        sanitizeFieldSpy = test.mocker.stub @cls.prototype, '_sanitizeField', -> '_f'
+        sanitizeValueSpy = test.mocker.stub @cls.prototype, '_sanitizeValue', -> '_v'
+
+        @inst._set('field1', 'value1', dummy: true)
+
+        assert.ok sanitizeFieldSpy.calledWithExactly 'field1'
+        assert.ok sanitizeValueSpy.calledWithExactly 'value1'
+
+        assert.same [ '_f' ], @inst._fields
+        assert.same [ [ '_v' ] ], @inst._values
+
+
+    '_setFields()':
+      'saves inputs': ->
+        @inst._setFields
+          'field1': 'value1'
+          'field2': 'value2'
+          'field3': 'value3'
+
+        expectedFields = [ 'field1', 'field2', 'field3' ]
+        expectedValues = [ [ 'value1', 'value2', 'value3'] ]
+        expectedFieldOptions = [ [ {}, {}, {} ] ]
+
+        assert.same expectedFields, @inst._fields
+        assert.same expectedValues, @inst._values
+        assert.same expectedFieldOptions, @inst._valueOptions
+
+      'sanitizes inputs': ->
+        sanitizeFieldSpy = test.mocker.stub @cls.prototype, '_sanitizeField', -> '_f'
+        sanitizeValueSpy = test.mocker.stub @cls.prototype, '_sanitizeValue', -> '_v'
+
+        @inst._setFields({'field1': 'value1'}, {dummy: true})
+
+        assert.ok sanitizeFieldSpy.calledWithExactly 'field1'
+        assert.ok sanitizeValueSpy.calledWithExactly 'value1'
+
+        assert.same [ '_f' ], @inst._fields
+        assert.same [ [ '_v' ] ], @inst._values
+
+    '_setFieldsRows()':
+      'saves inputs': ->
+        @inst._setFieldsRows [
+          {
+            'field1': 'value1'
+            'field2': 'value2'
+            'field3': 'value3'
+          }
+          {
+            'field1': 'value21'
+            'field2': 'value22'
+            'field3': 'value23'
+          }
+        ]
+
+        expectedFields = [ 'field1', 'field2', 'field3' ]
+        expectedValues = [ [ 'value1', 'value2', 'value3' ], [ 'value21', 'value22', 'value23' ] ]
+        expectedFieldOptions = [ [ {}, {}, {} ], [ {}, {}, {} ] ]
+
+        assert.same expectedFields, @inst._fields
+        assert.same expectedValues, @inst._values
+        assert.same expectedFieldOptions, @inst._valueOptions
+
+      'sanitizes inputs': ->
+        sanitizeFieldSpy = test.mocker.stub @cls.prototype, '_sanitizeField', -> return '_f'
+        sanitizeValueSpy = test.mocker.stub @cls.prototype, '_sanitizeValue', -> return '_v'
+
+        @inst._setFieldsRows [
+          {
+            'field1': 'value1'
+          },
+          {
+            'field1': 'value21'
+          }
+        ], { dummy: true }
+
+        assert.ok sanitizeFieldSpy.calledWithExactly 'field1'
+        assert.ok sanitizeValueSpy.calledWithExactly 'value1'
+        assert.ok sanitizeValueSpy.calledWithExactly 'value21'
+
+        assert.same [ '_f' ], @inst._fields
+        assert.same [ [ '_v' ], [ '_v' ] ], @inst._values
+
+    '_toParamString()': ->
+      assert.throws ( => @inst._toParamString()), 'Not yet implemented'
+
+
+
+
+  'SetFieldBlock':
+    beforeEach: ->
+      @cls = squel.cls.SetFieldBlock
+      @inst = new @cls()
+
+    'instanceof of AbstractSetFieldBlock': ->
+      assert.instanceOf @inst, squel.cls.AbstractSetFieldBlock
+
+    'set()':
+      'calls to _set()': ->
+        spy = test.mocker.stub @inst, '_set'
+
+        @inst.set 'f', 'v', dummy: true
+
+        assert.ok spy.calledWithExactly('f', 'v', dummy: true)
+
+    'setFields()':
+      'calls to _setFields()': ->
+        spy = test.mocker.stub @inst, '_setFields'
+
+        @inst.setFields 'f', dummy: true
+
+        assert.ok spy.calledWithExactly('f', dummy: true)
+
+
+    '_toParamString()':
+      'needs at least one field to have been provided': ->
+        try
+          @inst.toString()
+          throw new Error 'should not reach here'
+        catch err
+          assert.same 'Error: set() needs to be called', err.toString()
+
+      'fields set':
+        beforeEach: ->
+          @inst.set('field0 = field0 + 1')
+          @inst.set('field1', 'value1', { dummy: true })
+          @inst.set('field2', 'value2')
+          @inst.set('field3', squel.str('GETDATE(?)', 4))
+        'non-parameterized': ->
+          assert.same @inst._toParamString(), {
+            text: 'SET field0 = field0 + 1, field1 = \'value1\', field2 = \'value2\', field3 = (GETDATE(4))',
+            values: [],
+          }
+        'parameterized': ->
+          assert.same @inst._toParamString(buildParameterized: true), {
+            text: 'SET field0 = field0 + 1, field1 = ?, field2 = ?, field3 = (GETDATE(?))',
+            values: ['value1', 'value2', 4],
+          }
+
+
+
+  'InsertFieldValueBlock':
+    beforeEach: ->
+      @cls = squel.cls.InsertFieldValueBlock
+      @inst = new @cls()
+
+    'instanceof of AbstractSetFieldBlock': ->
+      assert.instanceOf @inst, squel.cls.AbstractSetFieldBlock
+
+    'set()':
+      'calls to _set()': ->
+        spy = test.mocker.stub @inst, '_set'
+
+        @inst.set 'f', 'v', dummy: true
+
+        assert.ok spy.calledWithExactly('f', 'v', dummy: true)
+
+    'setFields()':
+      'calls to _setFields()': ->
+        spy = test.mocker.stub @inst, '_setFields'
+
+        @inst.setFields 'f', dummy: true
+
+        assert.ok spy.calledWithExactly('f', dummy: true)
+
+    'setFieldsRows()':
+      'calls to _setFieldsRows()': ->
+        spy = test.mocker.stub @inst, '_setFieldsRows'
+
+        @inst.setFieldsRows 'f', dummy: true
+
+        assert.ok spy.calledWithExactly('f', dummy: true)
+
+    '_toParamString()':
+      'needs at least one field to have been provided': ->
+        assert.same '', @inst.toString()
+
+      'got fields':
+        beforeEach: ->
+          @inst.setFieldsRows([
+            { field1: 9, field2: 'value2', field3: squel.str('GETDATE(?)', 5) }
+            { field1: 8, field2: true, field3: null }
+          ])
+        'non-parameterized': ->
+          assert.same @inst._toParamString(), {
+            text: '(field1, field2, field3) VALUES (9, \'value2\', (GETDATE(5))), (8, TRUE, NULL)'
+            values: [],
+          }
+        'parameterized': ->
+          assert.same @inst._toParamString(buildParameterized: true), {
+            text: '(field1, field2, field3) VALUES (?, ?, (GETDATE(?))), (?, ?, ?)'
+            values: [9, 'value2', 5, 8, true, null],
+          }
+
+
+  'InsertFieldsFromQueryBlock':
+    beforeEach: ->
+      @cls = squel.cls.InsertFieldsFromQueryBlock
+      @inst = new @cls()
+
+    'instanceof of Block': ->
+      assert.instanceOf @inst, squel.cls.Block
+
+    'fromQuery()':
+      'sanitizes field names': ->
+        spy = test.mocker.stub @inst, '_sanitizeField', -> 1
+
+        qry = squel.select()
+
+        @inst.fromQuery(['test', 'one', 'two'], qry)
+
+        assert.ok spy.calledThrice
+        assert.ok spy.calledWithExactly 'test'
+        assert.ok spy.calledWithExactly 'one'
+        assert.ok spy.calledWithExactly 'two'
+
+      'sanitizes query': ->
+        spy = test.mocker.stub @inst, '_sanitizeBaseBuilder', -> 1
+
+        qry = 123
+
+        @inst.fromQuery(['test', 'one', 'two'], qry)
+
+        assert.ok spy.calledOnce
+        assert.ok spy.calledWithExactly qry
+
+      'overwrites existing values': ->
+        @inst._fields = 1
+        @inst._query = 2
+
+        qry = squel.select()
+        @inst.fromQuery(['test', 'one', 'two'], qry)
+
+        assert.same qry, @inst._query
+        assert.same ['test', 'one', 'two'], @inst._fields
+
+    '_toParamString()':
+      'needs fromQuery() to have been called': ->
+        assert.same @inst._toParamString(), {
+          text: ''
+          values: []
+        }
+
+      'default':
+        beforeEach: ->
+          @qry = squel.select().from('mega').where('a = ?', 5)
+          @inst.fromQuery ['test', 'one', 'two'], @qry          
+        'non-parameterized': ->
+          assert.same @inst._toParamString(), {
+            text: "(test, one, two) (SELECT * FROM mega WHERE (a = 5))"
+            values: []
+          }
+        'parameterized': ->
+          assert.same @inst._toParamString(buildParameterized: true), {
+            text: "(test, one, two) (SELECT * FROM mega WHERE (a = ?))"
+            values: [5]
+          }
+
+
+
+  'DistinctBlock':
+    beforeEach: ->
+      @cls = squel.cls.DistinctBlock
+      @inst = new @cls()
+
+    'instanceof of Block': ->
+      assert.instanceOf @inst, squel.cls.Block
+
+    '_toParamString()':
+      'output nothing if not set': ->
+        assert.same @inst._toParamString(), {
+          text: ''
+          values: []
+        }
+      'output DISTINCT if set': ->
+        @inst.distinct()
+        assert.same @inst._toParamString(), {
+          text: 'DISTINCT'
+          values: []
+        }
+
+
+
+
+  'GroupByBlock':
+    beforeEach: ->
+      @cls = squel.cls.GroupByBlock
+      @inst = new @cls()
+
+    'instanceof of Block': ->
+      assert.instanceOf @inst, squel.cls.Block
+
+    'group()':
+      'adds to list': ->
+        @inst.group('field1')
+        @inst.group('field2')
+
+        assert.same ['field1', 'field2'], @inst._groups
+
+      'sanitizes inputs': ->
+        sanitizeFieldSpy = test.mocker.stub @cls.prototype, '_sanitizeField', -> return '_f'
+
+        @inst.group('field1')
+
+        assert.ok sanitizeFieldSpy.calledWithExactly 'field1'
+
+        assert.same ['_f'], @inst._groups
+
+    'toString()':
+      'output nothing if no fields set': ->
+        @inst._groups = []
+        assert.same '', @inst.toString()
+
+      'output GROUP BY': ->
+        @inst.group('field1')
+        @inst.group('field2')
+
+        assert.same 'GROUP BY field1, field2', @inst.toString()
+
+
+
+
+  'OffsetBlock':
+    beforeEach: ->
+      @cls = squel.cls.OffsetBlock
+      @inst = new @cls()
+
+    'instanceof of Block': ->
+      assert.instanceOf @inst, squel.cls.Block
+
+    'offset()':
+      'set value': ->
+        @inst.offset(1)
+
+        assert.same 1, @inst._offsets
+
+        @inst.offset(22)
+
+        assert.same 22, @inst._offsets
+
+      'sanitizes inputs': ->
+        sanitizeSpy = test.mocker.stub @cls.prototype, '_sanitizeLimitOffset', -> return 234
+
+        @inst.offset(23)
+
+        assert.ok sanitizeSpy.calledWithExactly 23
+
+        assert.same 234, @inst._offsets
+
+    'toString()':
+      'output nothing if not set': ->
+        @inst._offsets = null
+        assert.same '', @inst.toString()
+
+      'output OFFSET': ->
+        @inst.offset(12)
+
+        assert.same 'OFFSET 12', @inst.toString()
+
+
+
+  'AbstractConditionBlock':
+    beforeEach: ->
+      @cls = squel.cls.AbstractConditionBlock
+      @inst = new @cls {
+        verb: 'ACB'
+      }
+
+      class squel.cls.MockConditionBlock extends squel.cls.AbstractConditionBlock
+        constructor: (options) ->
+          super _.extend({}, options, {verb: 'MOCKVERB'})
+
+        mockCondition: (condition, values...) ->
+          @_condition condition, values...
+
+      class squel.cls.MockSelectWithCondition extends squel.cls.Select
+        constructor: (options, blocks = null) ->
+          blocks = [
+            new squel.cls.StringBlock(options, 'SELECT'),
+            new squel.cls.GetFieldBlock(options),
+            new squel.cls.FromTableBlock(options),
+            new squel.cls.MockConditionBlock(options)
+          ]
+
+          super options, blocks
+
+    'instanceof of Block': ->
+      assert.instanceOf @inst, squel.cls.Block
+
+    '_condition()':
+      'adds to list': ->
+        @inst._condition('a = 1')
+        @inst._condition('b = 2 OR c = 3')
+
+        assert.same [
+          {
+            expr: 'a = 1'
+            values: []
+          }
+          {
+            expr: 'b = 2 OR c = 3'
+            values: []
+          }
+        ], @inst._conditions
+
+      'sanitizes inputs': ->
+        sanitizeFieldSpy = test.mocker.stub @cls.prototype, '_sanitizeExpression', -> return '_c'
+
+        @inst._condition('a = 1')
+
+        assert.ok sanitizeFieldSpy.calledWithExactly 'a = 1'
+
+        assert.same [{
+          expr: '_c'
+          values: []
+        }], @inst._conditions
+
+    '_toParamString()':
+      'output nothing if no conditions set': ->
+        assert.same @inst._toParamString(), {
+          text: ''
+          values: []
+        }
+
+      'output QueryBuilder ':
+        beforeEach: ->
+          subquery = new squel.cls.MockSelectWithCondition()
+          subquery.field('col1').from('table1').mockCondition('field1 = ?', 10)
+          @inst._condition('a in ?', subquery)
+          @inst._condition('b = ? OR c = ?', 2, 3)
+          @inst._condition('d in ?', [4, 5, 6])
+        'non-parameterized': ->
+          assert.same @inst._toParamString(), {
+            text: 'ACB (a in (SELECT col1 FROM table1 MOCKVERB (field1 = 10))) AND (b = 2 OR c = 3) AND (d in (4, 5, 6))'
+            values: []
+          }
+        'parameterized': ->
+          assert.same @inst._toParamString(buildParameterized: true), {
+            text: 'ACB (a in (SELECT col1 FROM table1 MOCKVERB (field1 = ?))) AND (b = ? OR c = ?) AND (d in (?, ?, ?))'
+            values: [10, 2, 3, 4, 5, 6]
+          }
+
+      'Fix for #64 - toString() does not change object':
+        beforeEach: ->
+          @inst._condition('a = ?', 1)
+          @inst._condition('b = ? OR c = ?', 2, 3)
+          @inst._condition('d in ?', [4, 5, 6])
+          @inst._toParamString()
+          @inst._toParamString()
+        'non-parameterized': ->
+          assert.same @inst._toParamString(), {
+            text: 'ACB (a = 1) AND (b = 2 OR c = 3) AND (d in (4, 5, 6))'
+            values: []
+          }
+        'parameterized': ->
+          assert.same @inst._toParamString(buildParameterized: true), {
+            text: 'ACB (a = ?) AND (b = ? OR c = ?) AND (d in (?, ?, ?))'
+            values: [1, 2, 3, 4, 5, 6]
+          }
+
+      'Fix for #226 - empty expressions':
+        beforeEach: ->
+          @inst._condition('a = ?', 1)
+          @inst._condition(squel.expr())
+        'non-parameterized': ->
+          assert.same @inst._toParamString(), {
+            text: 'ACB (a = 1)'
+            values: []
+          }
+        'parameterized': ->
+          assert.same @inst._toParamString(buildParameterized: true), {
+            text: 'ACB (a = ?)'
+            values: [1]
+          }
+
+
+
+  'WhereBlock':
+    beforeEach: ->
+      @cls = squel.cls.WhereBlock
+      @inst = new @cls()
+
+    'instanceof of AbstractConditionBlock': ->
+      assert.instanceOf @inst, squel.cls.AbstractConditionBlock
+
+    'sets verb to WHERE': ->
+      @inst = new @cls
+
+      assert.same 'WHERE', @inst.options.verb
+
+    '_toParamString()':
+      'output nothing if no conditions set': ->
+        assert.same @inst._toParamString(), {
+          text: ''
+          values: []
+        }
+
+      'output':
+        beforeEach: ->
+          subquery = new squel.cls.Select()
+          subquery.field('col1').from('table1').where('field1 = ?', 10)
+          @inst.where('a in ?', subquery)
+          @inst.where('b = ? OR c = ?', 2, 3)
+          @inst.where('d in ?', [4, 5, 6])
+        'non-parameterized': ->
+          assert.same @inst._toParamString(), { 
+            text: 'WHERE (a in (SELECT col1 FROM table1 WHERE (field1 = 10))) AND (b = 2 OR c = 3) AND (d in (4, 5, 6))'
+            values: []
+          }
+        'parameterized': ->
+          assert.same @inst._toParamString(buildParameterized: true), { 
+            text: 'WHERE (a in (SELECT col1 FROM table1 WHERE (field1 = ?))) AND (b = ? OR c = ?) AND (d in (?, ?, ?))'
+            values: [10, 2, 3, 4, 5, 6]
+          }
+
+
+
+  'HavingBlock':
+    beforeEach: ->
+      @cls = squel.cls.HavingBlock
+      @inst = new @cls()
+
+    'instanceof of AbstractConditionBlock': ->
+      assert.instanceOf @inst, squel.cls.AbstractConditionBlock
+
+    'sets verb': ->
+      @inst = new @cls
+
+      assert.same 'HAVING', @inst.options.verb
+
+    '_toParamString()':
+      'output nothing if no conditions set': ->
+        assert.same @inst._toParamString(), {
+          text: ''
+          values: []
+        }
+
+      'output':
+        beforeEach: ->
+          subquery = new squel.cls.Select()
+          subquery.field('col1').from('table1').where('field1 = ?', 10)
+          @inst.having('a in ?', subquery)
+          @inst.having('b = ? OR c = ?', 2, 3)
+          @inst.having('d in ?', [4, 5, 6])
+        'non-parameterized': ->
+          assert.same @inst._toParamString(), { 
+            text: 'HAVING (a in (SELECT col1 FROM table1 WHERE (field1 = 10))) AND (b = 2 OR c = 3) AND (d in (4, 5, 6))'
+            values: []
+          }
+        'parameterized': ->
+          assert.same @inst._toParamString(buildParameterized: true), { 
+            text: 'HAVING (a in (SELECT col1 FROM table1 WHERE (field1 = ?))) AND (b = ? OR c = ?) AND (d in (?, ?, ?))'
+            values: [10, 2, 3, 4, 5, 6]
+          }
+
+
+  'OrderByBlock':
+    beforeEach: ->
+      @cls = squel.cls.OrderByBlock
+      @inst = new @cls()
+
+    'instanceof of Block': ->
+      assert.instanceOf @inst, squel.cls.Block
+
+    'order()':
+      'adds to list': ->
+        @inst.order('field1')
+        @inst.order('field2', false)
+        @inst.order('field3', true)
+
+        expected = [
+          {
+            field: 'field1',
+            dir: true
+            values: []
+          },
+          {
+            field: 'field2',
+            dir: false
+            values: []
+          },
+          {
+            field: 'field3',
+            dir: true
+            values: []
+          }
+        ]
+
+        assert.same @inst._orders, expected
+
+      'sanitizes inputs': ->
+        sanitizeFieldSpy = test.mocker.stub @cls.prototype, '_sanitizeField', -> return '_f'
+
+        @inst.order('field1')
+
+        assert.ok sanitizeFieldSpy.calledWithExactly 'field1'
+
+        assert.same @inst._orders, [ { field: '_f', dir: true, values: [] } ]
+
+      'saves additional values': ->
+        @inst.order('field1', false, 1.2, 4)
+
+        assert.same @inst._orders, [ { field: 'field1', dir: false, values: [1.2, 4] } ]
+
+
+    '_toParamString()':
+      'empty': ->
+        assert.same @inst._toParamString(), { 
+          text: '', values: [] 
+        }
+
+      'default': ->
+        beforeEach: ->
+          @inst.order('field1')
+          @inst.order('field2', false)
+          @inst.order('GET(?, ?)', true, 2.5 ,5)
+        'non-parameterized': ->
+          assert.same @inst._toParamString(), { 
+            text: 'ORDER BY field1 ASC, field2 DESC, GET(2.5, 5) ASC', 
+            values: [] 
+          }
+        'parameterized': ->
+          assert.same @inst._toParamString(buildParameterized: true), { 
+            text: 'ORDER BY field1 ASC, field2 DESC, GET(?, ?) ASC', 
+            values: [2.5, 5] 
+          }
+
+
+  'LimitBlock':
+    beforeEach: ->
+      @cls = squel.cls.LimitBlock
+      @inst = new @cls()
+
+    'instanceof of Block': ->
+      assert.instanceOf @inst, squel.cls.Block
+
+    'limit()':
+      'set value': ->
+        @inst.limit(1)
+
+        assert.same 1, @inst._limit
+
+        @inst.limit(22)
+
+        assert.same 22, @inst._limit
+
+      'sanitizes inputs': ->
+        sanitizeSpy = test.mocker.stub @cls.prototype, '_sanitizeLimitOffset', -> return 234
+
+        @inst.limit(23)
+
+        assert.ok sanitizeSpy.calledWithExactly 23
+
+        assert.same 234, @inst._limit
+
+    '_toParamString()':
+      'output nothing if not set': ->
+        assert.same @inst._toParamString(), {
+          text: '',
+          values: []
+        }
+
+      'default':
+        beforeEach: ->
+          @inst.limit(10)
+        'non-parameterized': ->
+          assert.same @inst._toParamString(), {
+            text: 'LIMIT 10'
+            values: []
+          }
+        'parameterized': ->
+          assert.same @inst._toParamString(buildParameterized: true), {
+            text: 'LIMIT 10'
+            values: []
+          }
+
+
+  'JoinBlock':
+    beforeEach: ->
+      @cls = squel.cls.JoinBlock
+      @inst = new @cls()
+
+    'instanceof of Block': ->
+      assert.instanceOf @inst, squel.cls.Block
+
+    'join()':
+      'adds to list': ->
+        @inst.join('table1')
+        @inst.join('table2', null, 'b = 1', 'LEFT')
+        @inst.join('table3', 'alias3', 'c = 1', 'RIGHT')
+        @inst.join('table4', 'alias4', 'd = 1', 'OUTER')
+        @inst.join('table5', 'alias5', null, 'CROSS')
+
+        expected = [
+          {
+            type: 'INNER',
+            table: 'table1',
+            alias: null,
+            condition: null
+          },
+          {
+            type: 'LEFT',
+            table: 'table2',
+            alias: null,
+            condition: 'b = 1'
+          },
+          {
+            type: 'RIGHT',
+            table: 'table3',
+            alias: 'alias3',
+            condition: 'c = 1'
+          },
+          {
+            type: 'OUTER',
+            table: 'table4',
+            alias: 'alias4',
+            condition: 'd = 1'
+          },
+          {
+            type: 'CROSS',
+            table: 'table5',
+            alias: 'alias5',
+            condition: null
+          }
+        ]
+
+        assert.same @inst._joins, expected
+
+      'sanitizes inputs': ->
+        sanitizeTableSpy = test.mocker.stub @cls.prototype, '_sanitizeTable', -> return '_t'
+        sanitizeAliasSpy = test.mocker.stub @cls.prototype, '_sanitizeTableAlias', -> return '_a'
+        sanitizeConditionSpy = test.mocker.stub @cls.prototype, '_sanitizeExpression', -> return '_c'
+
+        @inst.join('table1', 'alias1', 'a = 1')
+
+        assert.ok sanitizeTableSpy.calledWithExactly 'table1', true
+        assert.ok sanitizeAliasSpy.calledWithExactly 'alias1'
+        assert.ok sanitizeConditionSpy.calledWithExactly 'a = 1'
+
+        expected = [
+          {
+          type: 'INNER',
+          table: '_t',
+          alias: '_a',
+          condition: '_c'
+          }
+        ]
+
+        assert.same @inst._joins, expected
+
+      'nested queries': ->
+        inner1 = squel.select()
+        inner2 = squel.select()
+        inner3 = squel.select()
+        inner4 = squel.select()
+        inner5 = squel.select()
+        inner6 = squel.select()
+        @inst.join(inner1)
+        @inst.join(inner2, null, 'b = 1', 'LEFT')
+        @inst.join(inner3, 'alias3', 'c = 1', 'RIGHT')
+        @inst.join(inner4, 'alias4', 'd = 1', 'OUTER')
+        @inst.join(inner5, 'alias5', 'e = 1', 'FULL')
+        @inst.join(inner6, 'alias6', null, 'CROSS')
+
+        expected = [
+          {
+          type: 'INNER',
+          table: inner1,
+          alias: null,
+          condition: null
+          },
+          {
+          type: 'LEFT',
+          table: inner2,
+          alias: null,
+          condition: 'b = 1'
+          },
+          {
+          type: 'RIGHT',
+          table: inner3,
+          alias: 'alias3',
+          condition: 'c = 1'
+          },
+          {
+          type: 'OUTER',
+          table: inner4,
+          alias: 'alias4',
+          condition: 'd = 1'
+          },
+          {
+            type: 'FULL',
+            table: inner5,
+            alias: 'alias5',
+            condition: 'e = 1'
+          },
+          {
+            type: 'CROSS',
+            table: inner6,
+            alias: 'alias6',
+            condition: null
+          }
+        ]
+
+        assert.same @inst._joins, expected
+
+    'left_join()':
+      'calls join()': ->
+        joinSpy = test.mocker.stub(@inst, 'join')
+
+        @inst.left_join('t', 'a', 'c')
+
+        assert.ok joinSpy.calledOnce
+        assert.ok joinSpy.calledWithExactly('t', 'a', 'c', 'LEFT')
+
+
+    '_toParamString()':
+      'output nothing if nothing set': ->
+        assert.same @inst._toParamString(), {
+          text: '',
+          values: []
+        }
+
+      'output JOINs with nested queries':
+        beforeEach: ->
+          inner2 = squel.select().function('GETDATE(?)', 2)
+          inner3 = squel.select().from('3')
+          inner4 = squel.select().from('4')
+          inner5 = squel.select().from('5')
+          expr = squel.expr().and('field1 = ?', 99)
+
+          @inst.join('table')
+          @inst.join(inner2, null, 'b = 1', 'LEFT')
+          @inst.join(inner3, 'alias3', 'c = 1', 'RIGHT')
+          @inst.join(inner4, 'alias4', 'e = 1', 'FULL')
+          @inst.join(inner5, 'alias5', expr, 'CROSS')
+
+        'non-parameterized': ->
+          assert.same @inst._toParamString(), {
+            text: 'INNER JOIN table LEFT JOIN (SELECT GETDATE(2)) ON (b = 1) RIGHT JOIN (SELECT * FROM 3) `alias3` ON (c = 1) FULL JOIN (SELECT * FROM 4) `alias4` ON (e = 1) CROSS JOIN (SELECT * FROM 5) `alias5` ON (field1 = 99)'
+            values: []
+          }
+        'parameterized': ->
+          assert.same @inst._toParamString(buildParameterized: true), {
+            text: 'INNER JOIN table LEFT JOIN (SELECT GETDATE(?)) ON (b = 1) RIGHT JOIN (SELECT * FROM 3) `alias3` ON (c = 1) FULL JOIN (SELECT * FROM 4) `alias4` ON (e = 1) CROSS JOIN (SELECT * FROM 5) `alias5` ON (field1 = ?)'
+            values: [2, 99]
+          }
+
+
+
+module?.exports[require('path').basename(__filename)] = test

+ 105 - 0
www/lib/squel/test/case.test.coffee

@@ -0,0 +1,105 @@
+###
+Copyright (c) 2014 Ramesh Nair (hiddentao.com)
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+###
+
+
+squel = require "../dist/squel-basic"
+{_, testCreator, assert, expect, should} = require './testbase'
+test = testCreator()
+
+
+test['Case expression builder base class'] =
+  beforeEach: ->
+    @func = squel.case
+    @inst = @func()
+    # manual return, 
+    # otherwise @inst will be treated a promise because it has a then() method
+    return 
+
+  'extends BaseBuilder': ->
+    assert.ok (@inst instanceof squel.cls.BaseBuilder)
+
+  'toString() returns NULL': ->
+    assert.same "NULL", @inst.toString()
+
+  'options':
+    'default options': ->
+      assert.same squel.cls.DefaultQueryBuilderOptions, @inst.options
+    'custom options': ->
+      e = @func({
+        separator: ',asdf'
+      })
+
+      expected = _.extend({}, squel.cls.DefaultQueryBuilderOptions, {
+        separator: ',asdf'  
+      })
+
+      assert.same expected, e.options
+
+  'build expression':
+    '>> when().then()':
+      beforeEach: -> 
+        @inst.when('?', 'foo').then('bar')
+        # manual return, 
+        # otherwise @inst will be treated a promise because it has a then() method
+        return
+
+      toString: ->
+        assert.same @inst.toString(), 'CASE WHEN (\'foo\') THEN \'bar\' ELSE NULL END'
+      toParam: ->
+        assert.same @inst.toParam(), { 
+          text: 'CASE WHEN (?) THEN \'bar\' ELSE NULL END', 
+          values: ['foo'] 
+        }
+
+    '>> when().then().else()':
+      beforeEach: -> 
+          @inst.when('?', 'foo').then('bar').else('foobar')
+          # manual return, 
+          # otherwise @inst will be treated a promise because it has a then() method
+          return
+      toString: ->
+        assert.same @inst.toString(), 'CASE WHEN (\'foo\') THEN \'bar\' ELSE \'foobar\' END'
+      toParam: ->
+        assert.same @inst.toParam(), { 
+          text: 'CASE WHEN (?) THEN \'bar\' ELSE \'foobar\' END', 
+          values: ['foo'] 
+        }
+        
+  'field case':
+    beforeEach: -> 
+      @inst = @func('name').when('?', 'foo').then('bar')
+      # manual return, 
+      # otherwise @inst will be treated a promise because it has a then() method
+      return      
+    toString: ->
+      assert.same @inst.toString(), 'CASE name WHEN (\'foo\') THEN \'bar\' ELSE NULL END'
+    toParam: ->
+      assert.same @inst.toParam(), { 
+        text: 'CASE name WHEN (?) THEN \'bar\' ELSE NULL END', 
+        values: ['foo'] 
+      }
+
+
+module?.exports[require('path').basename(__filename)] = test

+ 79 - 0
www/lib/squel/test/custom.test.coffee

@@ -0,0 +1,79 @@
+###
+Copyright (c) 2014 Ramesh Nair (hiddentao.com)
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+###
+
+
+squel = require "../dist/squel-basic"
+{_, testCreator, assert, expect, should} = require './testbase'
+test = testCreator()
+
+
+
+test['Custom queries'] =
+  'custom query': ->
+    class CommandBlock extends squel.cls.Block
+      command: (command, arg) ->
+        @_command = command
+        @_arg = arg
+      compress: (level) ->
+        @command('compress', level)
+      _toParamString: (options) ->
+        totalStr = @_command.toUpperCase()
+        totalValues = []
+
+        if not options.buildParameterized
+          totalStr += " #{@_arg}"
+        else
+          totalStr += " ?"
+          totalValues.push(@_arg)
+
+        {
+          text: totalStr,
+          values: totalValues,
+        }
+
+
+    class PragmaQuery extends squel.cls.QueryBuilder
+      constructor: (options) ->
+        blocks = [
+          new squel.cls.StringBlock(options, 'PRAGMA'),
+          new CommandBlock(options),
+        ]
+
+        super options, blocks
+
+    # squel method
+    squel.pragma = (options) -> new PragmaQuery(options)
+
+    qry = squel.pragma().compress(9)
+
+    assert.same qry.toString(), 'PRAGMA COMPRESS 9'
+    assert.same qry.toParam() , {
+      text: 'PRAGMA COMPRESS ?',
+      values: [9],
+    }
+
+
+
+module?.exports[require('path').basename(__filename)] = test

+ 145 - 0
www/lib/squel/test/delete.test.coffee

@@ -0,0 +1,145 @@
+###
+Copyright (c) 2014 Ramesh Nair (hiddentao.com)
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+###
+
+
+squel = require "../dist/squel-basic"
+{_, testCreator, assert, expect, should} = require './testbase'
+test = testCreator()
+
+
+
+test['DELETE builder'] =
+  beforeEach: ->
+    @func = squel.delete
+    @inst = @func()
+
+  'instanceof QueryBuilder': ->
+    assert.instanceOf @inst, squel.cls.QueryBuilder
+
+  'constructor':
+    'override options': ->
+      @inst = squel.update
+        usingValuePlaceholders: true
+        dummy: true
+
+      expectedOptions = _.extend {}, squel.cls.DefaultQueryBuilderOptions,
+        usingValuePlaceholders: true
+        dummy: true
+
+      for block in @inst.blocks
+        if (block instanceof squel.cls.WhereBlock)
+          assert.same _.extend({}, expectedOptions, { verb: 'WHERE'}), block.options
+        else
+          assert.same expectedOptions, block.options
+
+    'override blocks': ->
+      block = new squel.cls.StringBlock('SELECT')
+      @inst = @func {}, [block]
+      assert.same [block], @inst.blocks
+
+  'build query':
+    'no need to call from()': ->
+      @inst.toString()
+
+    '>> from(table)':
+      beforeEach: -> @inst.from('table')
+      toString: ->
+        assert.same @inst.toString(), 'DELETE FROM table'
+
+      '>> table(table2, t2)':
+        beforeEach: -> @inst.from('table2', 't2')
+        toString: ->
+          assert.same @inst.toString(), 'DELETE FROM table2 `t2`'
+
+        '>> where(a = 1)':
+          beforeEach: -> @inst.where('a = 1')
+          toString: ->
+            assert.same @inst.toString(), 'DELETE FROM table2 `t2` WHERE (a = 1)'
+
+          '>> join(other_table)':
+            beforeEach: -> @inst.join('other_table', 'o', 'o.id = t2.id')
+            toString: ->
+              assert.same @inst.toString(), 'DELETE FROM table2 `t2` INNER JOIN other_table `o` ON (o.id = t2.id) WHERE (a = 1)'
+
+            '>> order(a, true)':
+              beforeEach: -> @inst.order('a', true)
+              toString: ->
+                assert.same @inst.toString(), 'DELETE FROM table2 `t2` INNER JOIN other_table `o` ON (o.id = t2.id) WHERE (a = 1) ORDER BY a ASC'
+
+              '>> limit(2)':
+                beforeEach: -> @inst.limit(2)
+                toString: ->
+                  assert.same @inst.toString(), 'DELETE FROM table2 `t2` INNER JOIN other_table `o` ON (o.id = t2.id) WHERE (a = 1) ORDER BY a ASC LIMIT 2'
+
+
+    '>> target(table1).from(table1).left_join(table2, null, "table1.a = table2.b")':
+      beforeEach: ->
+        @inst.target('table1').from('table1').left_join('table2', null, 'table1.a = table2.b').where('c = ?', 3)
+      toString: ->
+        assert.same @inst.toString(),
+          'DELETE table1 FROM table1 LEFT JOIN table2 ON (table1.a = table2.b) WHERE (c = 3)'
+      toParam: ->
+        assert.same @inst.toParam(), 
+          {
+            text: 'DELETE table1 FROM table1 LEFT JOIN table2 ON (table1.a = table2.b) WHERE (c = ?)',
+            values: [3]
+          }
+
+      '>> target(table2)':
+        beforeEach: ->
+          @inst.target('table2')
+        toString: ->
+          assert.same @inst.toString(),
+            'DELETE table1, table2 FROM table1 LEFT JOIN table2 ON (table1.a = table2.b) WHERE (c = 3)'
+        toParam: ->
+          assert.same @inst.toParam(), 
+            {
+              text: 'DELETE table1, table2 FROM table1 LEFT JOIN table2 ON (table1.a = table2.b) WHERE (c = ?)',
+              values: [3]
+            }
+
+    '>> from(table1).left_join(table2, null, "table1.a = table2.b")':
+      beforeEach: ->
+        @inst.from('table1').left_join('table2', null, 'table1.a = table2.b').where('c = ?', 3)
+      toString: ->
+        assert.same @inst.toString(),
+          'DELETE FROM table1 LEFT JOIN table2 ON (table1.a = table2.b) WHERE (c = 3)'
+      toParam: ->
+        assert.same @inst.toParam(), 
+          {
+            text: 'DELETE FROM table1 LEFT JOIN table2 ON (table1.a = table2.b) WHERE (c = ?)',
+            values: [3]
+          }
+
+  'cloning': ->
+    newinst = @inst.from('students').limit(10).clone()
+    newinst.limit(20)
+
+    assert.same 'DELETE FROM students LIMIT 10', @inst.toString()
+    assert.same 'DELETE FROM students LIMIT 20', newinst.toString()
+
+
+
+module?.exports[require('path').basename(__filename)] = test

+ 330 - 0
www/lib/squel/test/expressions.test.coffee

@@ -0,0 +1,330 @@
+###
+Copyright (c) 2014 Ramesh Nair (hiddentao.com)
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+###
+
+
+squel = require "../dist/squel-basic"
+{_, testCreator, assert, expect, should} = require './testbase'
+test = testCreator()
+
+
+
+test['Expression builder base class'] =
+  beforeEach: ->
+    @inst = squel.expr()
+
+  'extends BaseBuilder': ->
+    assert.ok (@inst instanceof squel.cls.BaseBuilder)
+
+  'toString() returns empty': ->
+    assert.same "", @inst.toString()
+
+  'options':
+    'default options': ->
+      assert.same squel.cls.DefaultQueryBuilderOptions, @inst.options
+    'custom options': ->
+      e = squel.expr({
+        separator: ',asdf'
+      })
+
+      expected = _.extend({}, squel.cls.DefaultQueryBuilderOptions, {
+        separator: ',asdf'  
+      })
+
+      assert.same expected, e.options
+
+
+  'and()':
+    'without an argument throws an error': ->
+      assert.throws (=> @inst.and()), 'expression must be a string or Expression instance'
+    'with an array throws an error': ->
+      assert.throws (=> @inst.and([1])), 'expression must be a string or Expression instance'
+    'with an object throws an error': ->
+      assert.throws (=> @inst.and(new Object)), 'expression must be a string or Expression instance'
+    'with a function throws an error': ->
+      assert.throws (=> @inst.and(-> 1)), 'expression must be a string or Expression instance'
+    'with an Expression returns object instance': ->
+      assert.same @inst, @inst.and(squel.expr())
+    'with a string returns object instance': ->
+      assert.same @inst, @inst.and('bla')
+
+
+  'or()':
+    'without an argument throws an error': ->
+      assert.throws (=> @inst.or()), 'expression must be a string or Expression instance'
+    'with an array throws an error': ->
+      assert.throws (=> @inst.or([1])), 'expression must be a string or Expression instance'
+    'with an object throws an error': ->
+      assert.throws (=> @inst.or(new Object)), 'expression must be a string or Expression instance'
+    'with a function throws an error': ->
+      assert.throws (=> @inst.or(-> 1)), 'expression must be a string or Expression instance'
+    'with an Expression returns object instance': ->
+      assert.same @inst, @inst.or(squel.expr())      
+    'with a string returns object instance': ->
+      assert.same @inst, @inst.or('bla')
+
+
+  'and("test = 3")':
+    beforeEach: ->
+      @inst.and("test = 3")
+
+    '>> toString()': ->
+      assert.same @inst.toString(), 'test = 3'
+
+    '>> toParam()': ->
+      assert.same @inst.toParam(), { 
+        text: 'test = 3', 
+        values: [] 
+      }
+
+    '>> and("flight = \'4\'")':
+      beforeEach: ->
+        @inst.and("flight = '4'")
+
+      '>> toString()': ->
+        assert.same @inst.toString(), "test = 3 AND flight = '4'"
+
+      '>> toParam()': ->
+        assert.same @inst.toParam(), { 
+          text: "test = 3 AND flight = '4'", 
+          values: [] 
+        }
+
+      '>> or("dummy IN (1,2,3)")':
+        beforeEach: ->
+          @inst.or("dummy IN (1,2,3)")
+
+        '>> toString()': ->
+          assert.same @inst.toString(), "test = 3 AND flight = '4' OR dummy IN (1,2,3)"
+
+        '>> toParam()': ->
+          assert.same @inst.toParam(), { 
+            text: "test = 3 AND flight = '4' OR dummy IN (1,2,3)",
+            values: [],
+          }
+
+
+  'and("test = ?", null)':
+    beforeEach: ->
+      @inst.and("test = ?", null)
+
+    '>> toString()': ->
+      assert.same @inst.toString(), 'test = NULL'
+
+    '>> toParam()': ->
+      assert.same @inst.toParam(), {
+        text: 'test = ?'
+        values: [null]
+      }
+
+  'and("test = ?", 3)':
+    beforeEach: ->
+      @inst.and("test = ?", 3)
+
+    '>> toString()': ->
+      assert.same @inst.toString(), 'test = 3'
+
+    '>> toParam()': ->
+      assert.same @inst.toParam(), {
+        text: 'test = ?'
+        values: [3]
+      }
+
+    '>> and("flight = ?", "4")':
+      beforeEach: ->
+        @inst.and("flight = ?", '4')
+
+      '>> toString()': ->
+        assert.same @inst.toString(), "test = 3 AND flight = '4'"
+
+      '>> toParam()': ->
+        assert.same @inst.toParam(), {
+          text: "test = ? AND flight = ?"
+          values: [3, '4']
+        }
+
+      '>> or("dummy IN ?", [false, 2, null, "str"])':
+        beforeEach: ->
+          @inst.or("dummy IN ?", [false,2,null,"str"])
+
+        '>> toString()': ->
+          assert.same @inst.toString(), "test = 3 AND flight = '4' OR dummy IN (FALSE, 2, NULL, 'str')"
+
+        '>> toParam()': ->
+          assert.same @inst.toParam(), {
+            text: "test = ? AND flight = ? OR dummy IN (?, ?, ?, ?)"
+            values: [3, '4', false, 2, null, 'str']
+          }
+
+
+  'or("test = 3")':
+    beforeEach: ->
+      @inst.or("test = 3")
+
+    '>> toString()': ->
+      assert.same @inst.toString(), 'test = 3'
+
+    '>> toParam()': ->
+      assert.same @inst.toParam(), {
+        text: 'test = 3',
+        values: [],
+      }
+
+    '>> or("flight = \'4\'")':
+      beforeEach: ->
+        @inst.or("flight = '4'")
+
+      '>> toString()': ->
+        assert.same @inst.toString(), "test = 3 OR flight = '4'"
+
+      '>> toString()': ->
+        assert.same @inst.toParam(), {
+          text: "test = 3 OR flight = '4'",
+          values: [],
+        }
+
+      '>> and("dummy IN (1,2,3)")':
+        beforeEach: ->
+          @inst.and("dummy IN (1,2,3)")
+
+        '>> toString()': ->
+          assert.same @inst.toString(), "test = 3 OR flight = '4' AND dummy IN (1,2,3)"
+
+        '>> toParam()': ->
+          assert.same @inst.toParam(), {
+            text: "test = 3 OR flight = '4' AND dummy IN (1,2,3)",
+            values: [],
+          }
+
+
+  'or("test = ?", 3)':
+    beforeEach: ->
+      @inst.or("test = ?", 3)
+
+    '>> toString()': ->
+      assert.same @inst.toString(), 'test = 3'
+
+    '>> toParam()': ->
+      assert.same @inst.toParam(), {
+        text: 'test = ?'
+        values: [3]
+      }
+
+    '>> or("flight = ?", "4")':
+      beforeEach: ->
+        @inst.or("flight = ?", "4")
+
+      '>> toString()': ->
+        assert.same @inst.toString(), "test = 3 OR flight = '4'"
+
+      '>> toParam()': ->
+        assert.same @inst.toParam(), {
+          text: "test = ? OR flight = ?"
+          values: [3, '4']
+        }
+
+      '>> and("dummy IN ?", [false, 2, null, "str"])':
+        beforeEach: ->
+          @inst.and("dummy IN ?", [false, 2, null, "str"])
+
+        '>> toString()': ->
+          assert.same @inst.toString(), "test = 3 OR flight = '4' AND dummy IN (FALSE, 2, NULL, 'str')"
+
+        '>> toParam()': ->
+          assert.same @inst.toParam(), {
+            text: "test = ? OR flight = ? AND dummy IN (?, ?, ?, ?)"
+            values: [3, '4', false, 2, null, 'str']
+          }
+
+
+  'or("test = ?", 4)':
+    beforeEach: -> @inst.or("test = ?", 4)
+
+    '>> and(expr().or("inner = ?", 1))':
+      beforeEach: -> @inst.and( squel.expr().or('inner = ?', 1) )
+
+      '>> toString()': ->
+        assert.same @inst.toString(), "test = 4 AND (inner = 1)"
+
+      '>> toParam()': ->
+        assert.same @inst.toParam(), {
+          text: "test = ? AND (inner = ?)"
+          values: [4, 1]
+        }
+
+    '>> and(expr().or("inner = ?", 1).or(expr().and("another = ?", 34)))':
+      beforeEach: -> 
+          @inst.and( squel.expr().or('inner = ?', 1).or(squel.expr().and("another = ?", 34)) )
+
+      '>> toString()': ->
+        assert.same @inst.toString(), "test = 4 AND (inner = 1 OR (another = 34))"
+
+      '>> toParam()': ->
+        assert.same @inst.toParam(), {
+          text: "test = ? AND (inner = ? OR (another = ?))"
+          values: [4, 1, 34]
+        }
+
+
+  'custom parameter character: @@':
+    beforeEach: ->
+      @inst.options.parameterCharacter = '@@'
+
+    'and("test = @@", 3).and("flight = @@", "4").or("dummy IN @@", [false, 2, null, "str"])':
+      beforeEach: ->
+        @inst
+          .and("test = @@", 3)
+          .and("flight = @@", '4')
+          .or("dummy IN @@", [false,2,null,"str"])
+
+      '>> toString()': ->
+        assert.same @inst.toString(), "test = 3 AND flight = '4' OR dummy IN (FALSE, 2, NULL, 'str')"
+
+      '>> toParam()': ->
+        assert.same @inst.toParam(), {
+          text: "test = @@ AND flight = @@ OR dummy IN (@@, @@, @@, @@)"
+          values: [3, '4', false, 2, null, 'str']
+        }
+
+
+  'cloning': ->
+    newinst = @inst.or("test = 4").or("inner = 1").or("inner = 2").clone()
+    newinst.or('inner = 3')
+
+    assert.same @inst.toString(), 'test = 4 OR inner = 1 OR inner = 2'
+    assert.same newinst.toString(), 'test = 4 OR inner = 1 OR inner = 2 OR inner = 3'
+
+
+  'custom array prototype methods (Issue #210)': ->
+    Array.prototype.last = () -> 
+      this[this.length - 1]
+
+    @inst.or("foo = ?", "bar")
+
+    delete Array.prototype.last
+
+
+
+
+module?.exports[require('path').basename(__filename)] = test

+ 228 - 0
www/lib/squel/test/insert.test.coffee

@@ -0,0 +1,228 @@
+###
+Copyright (c) 2014 Ramesh Nair (hiddentao.com)
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+###
+
+
+squel = require "../dist/squel-basic"
+{_, testCreator, assert, expect, should} = require './testbase'
+test = testCreator()
+
+
+
+test['INSERT builder'] =
+  beforeEach: ->
+    @func = squel.insert
+    @inst = @func()
+
+  'instanceof QueryBuilder': ->
+    assert.instanceOf @inst, squel.cls.QueryBuilder
+
+  'constructor':
+    'override options': ->
+      @inst = squel.update
+        usingValuePlaceholders: true
+        dummy: true
+
+      expectedOptions = _.extend {}, squel.cls.DefaultQueryBuilderOptions,
+        usingValuePlaceholders: true
+        dummy: true
+
+      for block in @inst.blocks
+        if (block instanceof squel.cls.WhereBlock)
+          assert.same _.extend({}, expectedOptions, { verb: 'WHERE'}), block.options
+        else
+          assert.same expectedOptions, block.options
+
+
+    'override blocks': ->
+      block = new squel.cls.StringBlock('SELECT')
+      @inst = @func {}, [block]
+      assert.same [block], @inst.blocks
+
+
+  'build query':
+    'need to call into() first': ->
+      assert.throws (=> @inst.toString()), 'into() needs to be called'
+
+    'when set() not called': ->
+      assert.same 'INSERT INTO table', @inst.into('table').toString()
+
+    '>> into(table).set(field, null)':
+      beforeEach: -> @inst.into('table').set('field', null)
+      toString: ->
+        assert.same @inst.toString(), 'INSERT INTO table (field) VALUES (NULL)'
+      toParam: ->
+        assert.same @inst.toParam(), { text: 'INSERT INTO table (field) VALUES (?)', values: [null] }
+
+    '>> into(table)':
+      beforeEach: -> @inst.into('table')
+
+      '>> set(field, 1)':
+        beforeEach: -> @inst.set('field', 1)
+        toString: ->
+          assert.same @inst.toString(), 'INSERT INTO table (field) VALUES (1)'
+
+        '>> set(field2, 1.2)':
+          beforeEach: -> @inst.set('field2', 1.2)
+          toString: ->
+            assert.same @inst.toString(), 'INSERT INTO table (field, field2) VALUES (1, 1.2)'
+
+        '>> set(field2, "str")':
+          beforeEach: -> @inst.set('field2', 'str')
+          toString: ->
+            assert.same @inst.toString(), 'INSERT INTO table (field, field2) VALUES (1, \'str\')'
+          toParam: ->
+            assert.same @inst.toParam(), {
+              text: 'INSERT INTO table (field, field2) VALUES (?, ?)'
+              values: [ 1, 'str' ]
+            }
+
+        '>> set(field2, "str", { dontQuote: true } )':
+          beforeEach: -> @inst.set('field2', 'str', dontQuote: true)
+          toString: ->
+            assert.same @inst.toString(), 'INSERT INTO table (field, field2) VALUES (1, str)'
+          toParam: ->
+            assert.same @inst.toParam(), {
+              text: 'INSERT INTO table (field, field2) VALUES (?, ?)'
+              values: [ 1, 'str' ]
+            }
+
+        '>> set(field2, true)':
+          beforeEach: -> @inst.set('field2', true)
+          toString: ->
+            assert.same @inst.toString(), 'INSERT INTO table (field, field2) VALUES (1, TRUE)'
+
+        '>> set(field2, null)':
+          beforeEach: -> @inst.set('field2', null)
+          toString: ->
+            assert.same @inst.toString(), 'INSERT INTO table (field, field2) VALUES (1, NULL)'
+
+        '>> set(field, query builder)':
+          beforeEach: ->
+            @subQuery = squel.select().field('MAX(score)').from('scores')
+            @inst.set( 'field',  @subQuery )
+          toString: ->
+            assert.same @inst.toString(), 'INSERT INTO table (field) VALUES ((SELECT MAX(score) FROM scores))'
+          toParam: ->
+            parameterized = @inst.toParam()
+            assert.same parameterized.text, 'INSERT INTO table (field) VALUES ((SELECT MAX(score) FROM scores))'
+            assert.same parameterized.values, []
+
+        '>> setFields({field2: \'value2\', field3: true })':
+          beforeEach: -> @inst.setFields({field2: 'value2', field3: true })
+          toString: ->
+            assert.same @inst.toString(), 'INSERT INTO table (field, field2, field3) VALUES (1, \'value2\', TRUE)'
+          toParam: ->
+            parameterized = @inst.toParam()
+            assert.same parameterized.text, 'INSERT INTO table (field, field2, field3) VALUES (?, ?, ?)'
+            assert.same parameterized.values, [1,'value2',true]
+
+        '>> setFields({field2: \'value2\', field: true })':
+          beforeEach: -> @inst.setFields({field2: 'value2', field: true })
+          toString: ->
+            assert.same @inst.toString(), 'INSERT INTO table (field, field2) VALUES (TRUE, \'value2\')'
+          toParam: ->
+            parameterized = @inst.toParam()
+            assert.same parameterized.text, 'INSERT INTO table (field, field2) VALUES (?, ?)'
+            assert.same parameterized.values, [true, 'value2']
+
+        '>> setFields(custom value type)':
+          beforeEach: ->
+            class MyClass
+            @inst.registerValueHandler MyClass, -> 'abcd'
+            @inst.setFields({ field: new MyClass() })
+          toString: ->
+            assert.same @inst.toString(), 'INSERT INTO table (field) VALUES ((abcd))'
+          toParam: ->
+            parameterized = @inst.toParam()
+            assert.same parameterized.text, 'INSERT INTO table (field) VALUES (?)'
+            assert.same parameterized.values, ['abcd']
+
+        '>> setFieldsRows([{field: \'value2\', field2: true },{field: \'value3\', field2: 13 }]])':
+          beforeEach: -> @inst.setFieldsRows([{field: 'value2', field2: true },{field: 'value3', field2: 13 }])
+          toString: ->
+            assert.same @inst.toString(), 'INSERT INTO table (field, field2) VALUES (\'value2\', TRUE), (\'value3\', 13)'
+          toParam: ->
+            parameterized = @inst.toParam()
+            assert.same parameterized.text, 'INSERT INTO table (field, field2) VALUES (?, ?), (?, ?)'
+            assert.same parameterized.values, ['value2',true, 'value3',13]
+
+      'Function values':
+        beforeEach: -> @inst.set('field', squel.str('GETDATE(?, ?)', 2014, 'feb'))
+        toString: ->
+          assert.same 'INSERT INTO table (field) VALUES ((GETDATE(2014, \'feb\')))', @inst.toString()
+        toParam: ->  
+          assert.same { text: 'INSERT INTO table (field) VALUES ((GETDATE(?, ?)))', values: [2014, 'feb'] }, @inst.toParam()
+
+      '>> fromQuery([field1, field2], select query)':
+        beforeEach: -> @inst.fromQuery(
+            ['field1', 'field2'],
+            squel.select().from('students').where('a = ?', 2)
+          )
+        toString: ->
+          assert.same @inst.toString(), 'INSERT INTO table (field1, field2) (SELECT * FROM students WHERE (a = 2))'
+        toParam: ->
+          parameterized = @inst.toParam()
+          assert.same parameterized.text, 'INSERT INTO table (field1, field2) (SELECT * FROM students WHERE (a = ?))'
+          assert.same parameterized.values, [ 2 ]
+
+      '>> setFieldsRows([{field1: 13, field2: \'value2\'},{field1: true, field3: \'value4\'}])': ->
+        assert.throws (=> @inst.setFieldsRows([{field1: 13, field2: 'value2'},{field1: true, field3: 'value4'}]).toString()), 'All fields in subsequent rows must match the fields in the first row'
+
+
+  'dontQuote and replaceSingleQuotes set(field2, "ISNULL(\'str\', str)", { dontQuote: true })':
+    beforeEach: ->
+      @inst = squel.insert replaceSingleQuotes: true
+      @inst.into('table').set('field', 1)
+      @inst.set('field2', "ISNULL('str', str)", dontQuote: true)
+    toString: ->
+      assert.same @inst.toString(), 'INSERT INTO table (field, field2) VALUES (1, ISNULL(\'str\', str))'
+    toParam: ->
+      assert.same @inst.toParam(), {
+        text: 'INSERT INTO table (field, field2) VALUES (?, ?)'
+        values: [1, "ISNULL('str', str)"]
+      }
+
+  'fix for #225 - autoquoting field names': ->
+    @inst = squel.insert(autoQuoteFieldNames: true)
+      .into('users')
+      .set('active', 1)
+      .set('regular', 0)
+      .set('moderator',1)
+
+      assert.same @inst.toParam(), {
+        text: 'INSERT INTO users (`active`, `regular`, `moderator`) VALUES (?, ?, ?)',
+        values: [1, 0, 1],
+      }
+
+  'cloning': ->
+    newinst = @inst.into('students').set('field', 1).clone()
+    newinst.set('field', 2).set('field2', true)
+
+    assert.same 'INSERT INTO students (field) VALUES (1)', @inst.toString()
+    assert.same 'INSERT INTO students (field, field2) VALUES (2, TRUE)', newinst.toString()
+
+
+
+module?.exports[require('path').basename(__filename)] = test

+ 161 - 0
www/lib/squel/test/mssql.test.coffee

@@ -0,0 +1,161 @@
+###
+Copyright (c) Ramesh Nair (hiddentao.com)
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+###
+
+
+squel = undefined
+{_, testCreator, assert, expect, should} = require './testbase'
+test = testCreator()
+
+
+
+test['MSSQL flavour'] =
+  beforeEach: ->
+    delete require.cache[require.resolve('../dist/squel')]
+    squel = require "../dist/squel"
+    squel = squel.useFlavour 'mssql'
+
+  'DATE Conversion':
+    beforeEach: -> @inst = squel.insert()
+
+    '>> into(table).set(field, new Date(2012-12-12T4:30:00Z))':
+      beforeEach: -> @inst.into('table').set('field', new Date("2012-12-12T04:30:00Z"))
+      toString: ->
+        assert.same @inst.toString(), 'INSERT INTO table (field) VALUES ((\'2012-12-12 4:30:0\'))'
+
+  'SELECT builder':
+    beforeEach: ->
+      @sel = squel.select()
+
+    '>> from(table).field(field).top(10)':
+      beforeEach: -> @sel.from('table').field('field').top(10)
+      toString: ->
+        assert.same @sel.toString(), 'SELECT TOP (10) field FROM table'
+
+    '>> from(table).field(field).limit(10)':
+      beforeEach: -> @sel.from('table').field('field').limit(10)
+      toString: ->
+        assert.same @sel.toString(), 'SELECT TOP (10) field FROM table'
+
+    '>> from(table).field(field).limit(10).offset(5)':
+      beforeEach: -> @sel.from('table').field('field').limit(10).offset(5)
+      toString: ->
+        assert.same @sel.toString(), 'SELECT field FROM table OFFSET 5 ROWS FETCH NEXT 10 ROWS ONLY'
+
+    '>> from(table).field(field).top(10).offset(5)':
+      beforeEach: -> @sel.from('table').field('field').top(10).offset(5)
+      toString: ->
+        assert.same @sel.toString(), 'SELECT field FROM table OFFSET 5 ROWS FETCH NEXT 10 ROWS ONLY'
+
+    '>> from(table).field(field).offset(5)':
+      beforeEach: -> @sel.from('table').field('field').offset(5)
+      toString: ->
+        assert.same @sel.toString(), 'SELECT field FROM table OFFSET 5 ROWS'
+
+    '>> from(table).field(field).offset(5).union(...)':
+      beforeEach: -> @sel.from('table').field('field').offset(5).union(squel.select().from('table2').where('a = 2'))
+      toString: ->
+        assert.same @sel.toString(), 'SELECT field FROM table OFFSET 5 ROWS UNION (SELECT * FROM table2 WHERE (a = 2))'
+
+    '>> check variables arent being shared':
+      toString: ->
+        assert.same squel.select().from('table').field('field').top(10).toString(), 'SELECT TOP (10) field FROM table'
+        assert.same squel.select().from('table').field('field').toString(), 'SELECT field FROM table'
+
+  'INSERT builder':
+    beforeEach: -> @inst = squel.insert()
+
+    '>> into(table).set(field, 1).output(id)':
+      beforeEach: -> @inst.into('table').output('id').set('field', 1)
+      toString: ->
+        assert.same @inst.toString(), 'INSERT INTO table (field) OUTPUT INSERTED.id VALUES (1)'
+
+  'UPDATE builder':
+    beforeEach: -> @upt = squel.update()
+
+    '>> table(table).set(field, 1).top(12)':
+      beforeEach: -> @upt.table('table').set('field', 1).top(12)
+      toString: ->
+        assert.same @upt.toString(), 'UPDATE TOP (12) table SET field = 1'
+
+    '>> table(table).set(field, 1).limit(12)':
+      beforeEach: -> @upt.table('table').set('field', 1).limit(12)
+      toString: ->
+        assert.same @upt.toString(), 'UPDATE TOP (12) table SET field = 1'
+
+    '>> table(table).set(field, 1).output(id)':
+      beforeEach: -> @upt.table('table').output('id').set('field', 1)
+      toString: ->
+        assert.same @upt.toString(), 'UPDATE table SET field = 1 OUTPUT INSERTED.id'
+
+    '>> table(table).set(field, 1).outputs(id AS ident, name AS naming)':
+      beforeEach: -> @upt.table('table').outputs(
+      	id: 'ident'
+      	name: 'naming'
+      ).set('field', 1)
+      toString: ->
+        assert.same @upt.toString(), 'UPDATE table SET field = 1 OUTPUT INSERTED.id AS ident, INSERTED.name AS naming'
+
+  'DELETE builder':
+    beforeEach: -> @upt = squel.delete()
+
+    '>> from(table)':
+      beforeEach: -> @upt.from('table')
+      toString: ->
+        assert.same @upt.toString(), 'DELETE FROM table'
+
+    '>> from(table).output(id)':
+      beforeEach: -> @upt.from('table').output('id')
+      toString: ->
+        assert.same @upt.toString(), 'DELETE FROM table OUTPUT DELETED.id'
+
+    '>> from(table).outputs(id AS ident, name AS naming).where("a = 1")':
+      beforeEach: -> @upt.from('table').outputs(
+        id: 'ident'
+        name: 'naming'
+      ).where('a = 1')
+      toString: ->
+        assert.same @upt.toString(), 'DELETE FROM table OUTPUT DELETED.id AS ident, DELETED.name AS naming WHERE (a = 1)'
+
+  'Default query builder options': ->
+    assert.same {
+      autoQuoteTableNames: false
+      autoQuoteFieldNames: false
+      autoQuoteAliasNames: false
+      useAsForTableAliasNames: false
+      nameQuoteCharacter: '`'
+      tableAliasQuoteCharacter: '`'
+      fieldAliasQuoteCharacter: '"'
+      valueHandlers: []
+      parameterCharacter: '?'
+      numberedParameters: false
+      numberedParametersPrefix: '@'
+      numberedParametersStartAt: 1
+      replaceSingleQuotes: true
+      singleQuoteReplacement: '\'\''
+      separator: ' '
+      stringFormatter: null
+    }, squel.cls.DefaultQueryBuilderOptions
+
+module?.exports[require('path').basename(__filename)] = test

+ 109 - 0
www/lib/squel/test/mysql.test.coffee

@@ -0,0 +1,109 @@
+###
+Copyright (c) Ramesh Nair (hiddentao.com)
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+###
+
+
+squel = undefined
+{_, testCreator, assert, expect, should} = require './testbase'
+test = testCreator()
+
+
+test['MySQL flavour'] =
+  beforeEach: ->
+    delete require.cache[require.resolve('../dist/squel')]
+    squel = require "../dist/squel"
+    squel = squel.useFlavour 'mysql'
+
+
+  'MysqlOnDuplicateKeyUpdateBlock':
+    beforeEach: ->
+      @cls = squel.cls.MysqlOnDuplicateKeyUpdateBlock
+      @inst = new @cls()
+
+    'instanceof of AbstractSetFieldBlock': ->
+      assert.instanceOf @inst, squel.cls.AbstractSetFieldBlock
+
+    'onDupUpdate()':
+      'calls to _set()': ->
+        spy = test.mocker.stub @inst, '_set'
+
+        @inst.onDupUpdate 'f', 'v', dummy: true
+
+        assert.ok spy.calledWithExactly('f', 'v', dummy: true)
+
+
+    '_toParamString()':
+      beforeEach: ->
+        @inst.onDupUpdate('field1 = field1 + 1')
+        @inst.onDupUpdate('field2', 'value2', {dummy: true})
+        @inst.onDupUpdate('field3', 'value3')
+
+      'non-parameterized': ->
+        assert.same @inst._toParamString(), {
+          text: 'ON DUPLICATE KEY UPDATE field1 = field1 + 1, field2 = \'value2\', field3 = \'value3\''
+          values: []
+        }
+      'parameterized': ->
+        assert.same @inst._toParamString(buildParameterized: true), {
+          text: 'ON DUPLICATE KEY UPDATE field1 = field1 + 1, field2 = ?, field3 = ?'
+          values: ['value2', 'value3']
+        }
+
+
+  'INSERT builder':
+    beforeEach: -> @inst = squel.insert()
+
+    '>> into(table).set(field, 1).set(field1, 2).onDupUpdate(field, 5).onDupUpdate(field1, "str")':
+      beforeEach: ->
+        @inst
+          .into('table')
+          .set('field', 1)
+          .set('field1', 2)
+          .onDupUpdate('field', 5)
+          .onDupUpdate('field1', 'str')
+      toString: ->
+        assert.same @inst.toString(), 'INSERT INTO table (field, field1) VALUES (1, 2) ON DUPLICATE KEY UPDATE field = 5, field1 = \'str\''
+
+      toParam: ->
+        assert.same @inst.toParam(), {
+          text: 'INSERT INTO table (field, field1) VALUES (?, ?) ON DUPLICATE KEY UPDATE field = ?, field1 = ?'
+          values: [1, 2, 5, 'str']
+        }
+
+    '>> into(table).set(field2, 3).onDupUpdate(field2, "str", { dontQuote: true })':
+      beforeEach: ->
+        @inst
+          .into('table')
+          .set('field2', 3)
+          .onDupUpdate('field2', 'str', { dontQuote: true })
+      toString: ->
+        assert.same @inst.toString(), 'INSERT INTO table (field2) VALUES (3) ON DUPLICATE KEY UPDATE field2 = str'
+      toParam: ->
+        assert.same @inst.toParam(), {
+          text: 'INSERT INTO table (field2) VALUES (?) ON DUPLICATE KEY UPDATE field2 = ?'
+          values: [3, 'str']
+        }
+
+
+module?.exports[require('path').basename(__filename)] = test

+ 268 - 0
www/lib/squel/test/postgres.test.coffee

@@ -0,0 +1,268 @@
+###
+Copyright (c) 2014 Ramesh Nair (hiddentao.com)
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+###
+
+
+squel = undefined
+{_, testCreator, assert, expect, should} = require './testbase'
+test = testCreator()
+
+
+test['Postgres flavour'] =
+  beforeEach: ->
+    delete require.cache[require.resolve('../dist/squel')]
+    squel = require "../dist/squel"
+    squel = squel.useFlavour 'postgres'
+
+  'INSERT builder':
+    beforeEach: -> @inst = squel.insert()
+
+    '>> into(table).set(field, 1).returning("*")':
+      beforeEach: -> @inst.into('table').set('field', 1).returning('*')
+      toString: ->
+        assert.same @inst.toString(), 'INSERT INTO table (field) VALUES (1) RETURNING *'
+
+    '>> into(table).set(field, 1).returning("id")':
+      beforeEach: -> @inst.into('table').set('field', 1).returning('id')
+      toString: ->
+        assert.same @inst.toString(), 'INSERT INTO table (field) VALUES (1) RETURNING id'
+
+    '>> into(table).set(field, 1).with(alias, table)':
+      beforeEach: -> @inst.into('table').set('field', 1).with('alias', squel.select().from('table').where('field = ?', 2))
+      toString: ->
+        assert.same @inst.toString(), 'WITH alias AS (SELECT * FROM table WHERE (field = 2)) INSERT INTO table (field) VALUES (1)'
+      toParam: ->
+        assert.same @inst.toParam(), {
+          "text": 'WITH alias AS (SELECT * FROM table WHERE (field = $1)) INSERT INTO table (field) VALUES ($2)',
+          "values": [2, 1]
+        }
+
+  'UPDATE builder':
+    beforeEach: -> @upd = squel.update()
+
+    '>> table(table).set(field, 1).returning("*")':
+      beforeEach: -> @upd.table('table').set('field', 1).returning('*')
+      toString: ->
+        assert.same @upd.toString(), 'UPDATE table SET field = 1 RETURNING *'
+
+    '>> table(table).set(field, 1).returning("field")':
+      beforeEach: -> @upd.table('table').set('field', 1).returning('field')
+      toString: ->
+        assert.same @upd.toString(), 'UPDATE table SET field = 1 RETURNING field'
+
+    '>> table(table).set(field, 1).from(table2)':
+      beforeEach: -> @upd.table('table').set('field', 1).from('table2')
+      toString: ->
+        assert.same @upd.toString(), 'UPDATE table SET field = 1 FROM table2'
+
+    '>> table(table).set(field, 1).with(alias, table)':
+      beforeEach: -> @upd.table('table').set('field', 1).with('alias', squel.select().from('table').where('field = ?', 2))
+      toString: ->
+        assert.same @upd.toString(), 'WITH alias AS (SELECT * FROM table WHERE (field = 2)) UPDATE table SET field = 1'
+      toParam: ->
+        assert.same @upd.toParam(), {
+          "text": 'WITH alias AS (SELECT * FROM table WHERE (field = $1)) UPDATE table SET field = $2',
+          "values": [2, 1]
+        }
+
+  'DELETE builder':
+    beforeEach: -> @del = squel.delete()
+
+    '>> from(table).where(field = 1).returning("*")':
+      beforeEach: -> @del.from('table').where('field = 1').returning('*')
+      toString: ->
+        assert.same @del.toString(), 'DELETE FROM table WHERE (field = 1) RETURNING *'
+
+    '>> from(table).where(field = 1).returning("field")':
+      beforeEach: -> @del.from('table').where('field = 1').returning('field')
+      toString: ->
+        assert.same @del.toString(), 'DELETE FROM table WHERE (field = 1) RETURNING field'
+
+    '>> from(table).where(field = 1).with(alias, table)':
+      beforeEach: -> @del.from('table').where('field = ?', 1).with('alias', squel.select().from('table').where('field = ?', 2))
+      toString: ->
+        assert.same @del.toString(), 'WITH alias AS (SELECT * FROM table WHERE (field = 2)) DELETE FROM table WHERE (field = 1)'
+      toParam: ->
+        assert.same @del.toParam(), {
+          "text": 'WITH alias AS (SELECT * FROM table WHERE (field = $1)) DELETE FROM table WHERE (field = $2)',
+          "values": [2, 1]
+        }
+
+  'SELECT builder':
+    beforeEach: ->
+      @sel = squel.select()
+    'select':
+      '>> from(table).where(field = 1)':
+        beforeEach: ->
+          @sel.field('field1').from('table1').where('field1 = 1')
+        toString: ->
+          assert.same @sel.toString(), 'SELECT field1 FROM table1 WHERE (field1 = 1)'
+        toParam: ->
+          assert.same @sel.toParam(), {
+            "text": 'SELECT field1 FROM table1 WHERE (field1 = 1)'
+            "values": []
+          }
+
+      '>> from(table).where(field = ?, 2)':
+        beforeEach: ->
+          @sel.field('field1').from('table1').where('field1 = ?', 2)
+        toString: ->
+          assert.same @sel.toString(), 'SELECT field1 FROM table1 WHERE (field1 = 2)'
+        toParam: ->
+          assert.same @sel.toParam(), {
+            "text": 'SELECT field1 FROM table1 WHERE (field1 = $1)'
+            "values": [2]
+          }
+
+    'distinct queries':
+      beforeEach: ->
+        @sel.fields(['field1', 'field2']).from('table1')
+
+      '>> from(table).distinct()':
+        beforeEach: ->
+          @sel.distinct()
+        toString: ->
+          assert.same @sel.toString(), 'SELECT DISTINCT field1, field2 FROM table1'
+        toParam: ->
+          assert.same @sel.toParam(), {
+            'text': 'SELECT DISTINCT field1, field2 FROM table1',
+            'values': []
+          }
+
+      '>> from(table).distinct(field1)':
+        beforeEach: ->
+          @sel.distinct('field1')
+        toString: ->
+          assert.same @sel.toString(), 'SELECT DISTINCT ON (field1) field1, field2 FROM table1'
+        toParam: ->
+          assert.same @sel.toParam(), {
+            'text': 'SELECT DISTINCT ON (field1) field1, field2 FROM table1',
+            'values': []
+          }
+
+      '>> from(table).distinct(field1, field2)':
+        beforeEach: ->
+          @sel.distinct('field1', 'field2')
+        toString: ->
+          assert.same @sel.toString(), 'SELECT DISTINCT ON (field1, field2) field1, field2 FROM table1'
+        toParam: ->
+          assert.same @sel.toParam(), {
+            'text': 'SELECT DISTINCT ON (field1, field2) field1, field2 FROM table1',
+            'values': []
+          }
+
+    'cte queries':
+      beforeEach: ->
+        @sel = squel.select()
+        @sel2 = squel.select()
+        @sel3 = squel.select()
+
+      '>> query1.with(alias, query2)':
+        beforeEach: ->
+          @sel.from('table1').where('field1 = ?', 1)
+          @sel2.from('table2').where('field2 = ?', 2)
+          @sel.with('someAlias', @sel2)
+        toString: ->
+          assert.same @sel.toString(), 'WITH someAlias AS (SELECT * FROM table2 WHERE (field2 = 2)) SELECT * FROM table1 WHERE (field1 = 1)'
+        toParam: ->
+          assert.same @sel.toParam(), {
+            "text": 'WITH someAlias AS (SELECT * FROM table2 WHERE (field2 = $1)) SELECT * FROM table1 WHERE (field1 = $2)'
+            "values": [2, 1]
+          }
+
+      '>> query1.with(alias1, query2).with(alias2, query2)':
+        beforeEach: ->
+          @sel.from('table1').where('field1 = ?', 1)
+          @sel2.from('table2').where('field2 = ?', 2)
+          @sel3.from('table3').where('field3 = ?', 3)
+          @sel.with('someAlias', @sel2).with('anotherAlias', @sel3)
+        toString: ->
+          assert.same @sel.toString(), 'WITH someAlias AS (SELECT * FROM table2 WHERE (field2 = 2)), anotherAlias AS (SELECT * FROM table3 WHERE (field3 = 3)) SELECT * FROM table1 WHERE (field1 = 1)'
+        toParam: ->
+          assert.same @sel.toParam(), {
+            "text": 'WITH someAlias AS (SELECT * FROM table2 WHERE (field2 = $1)), anotherAlias AS (SELECT * FROM table3 WHERE (field3 = $2)) SELECT * FROM table1 WHERE (field1 = $3)'
+            "values": [2, 3, 1]
+          }
+
+
+    'union queries':
+      beforeEach: ->
+        @sel = squel.select()
+        @sel2 = squel.select()
+
+      '>> query1.union(query2)':
+        beforeEach: ->
+          @sel.field('field1').from('table1').where('field1 = ?', 3)
+          @sel2.field('field1').from('table1').where('field1 < ?', 10)
+          @sel.union(@sel2)
+        toString: ->
+          assert.same @sel.toString(), 'SELECT field1 FROM table1 WHERE (field1 = 3) UNION (SELECT field1 FROM table1 WHERE (field1 < 10))'
+        toParam: ->
+          assert.same @sel.toParam(), {
+            "text": 'SELECT field1 FROM table1 WHERE (field1 = $1) UNION (SELECT field1 FROM table1 WHERE (field1 < $2))'
+            "values": [
+              3
+              10
+            ]
+          }
+
+      '>> query1.union_all(query2)':
+        beforeEach: ->
+          @sel.field('field1').from('table1').where('field1 = ?', 3)
+          @sel2.field('field1').from('table1').where('field1 < ?', 10)
+          @sel.union_all(@sel2)
+        toString: ->
+          assert.same @sel.toString(), 'SELECT field1 FROM table1 WHERE (field1 = 3) UNION ALL (SELECT field1 FROM table1 WHERE (field1 < 10))'
+        toParam: ->
+          assert.same @sel.toParam(), {
+            "text": 'SELECT field1 FROM table1 WHERE (field1 = $1) UNION ALL (SELECT field1 FROM table1 WHERE (field1 < $2))'
+            "values": [
+              3
+              10
+            ]
+          }
+
+
+  'Default query builder options': ->
+    assert.same {
+      replaceSingleQuotes: false
+      singleQuoteReplacement: '\'\''
+      autoQuoteTableNames: false
+      autoQuoteFieldNames: false
+      autoQuoteAliasNames: false
+      useAsForTableAliasNames: true
+      nameQuoteCharacter: '`'
+      tableAliasQuoteCharacter: '`'
+      fieldAliasQuoteCharacter: '"'
+      valueHandlers: []
+      parameterCharacter: '?'
+      numberedParameters: true
+      numberedParametersPrefix: '$'
+      numberedParametersStartAt: 1
+      separator: ' '
+      stringFormatter: null
+    }, squel.cls.DefaultQueryBuilderOptions
+
+
+module?.exports[require('path').basename(__filename)] = test

+ 481 - 0
www/lib/squel/test/select.test.coffee

@@ -0,0 +1,481 @@
+###
+Copyright (c) 2014 Ramesh Nair (hiddentao.com)
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+###
+
+
+squel = require "../dist/squel-basic"
+{_, testCreator, assert, expect, should} = require './testbase'
+test = testCreator()
+
+
+
+test['SELECT builder'] =
+  beforeEach: ->
+    @func = squel.select
+    @inst = @func()
+
+  'instanceof QueryBuilder': ->
+    assert.instanceOf @inst, squel.cls.QueryBuilder
+
+  'constructor':
+    'override options': ->
+      @inst = squel.select
+        usingValuePlaceholders: true
+        dummy: true
+
+      expectedOptions = _.extend {}, squel.cls.DefaultQueryBuilderOptions,
+        usingValuePlaceholders: true
+        dummy: true
+
+      for block in @inst.blocks
+        if (block instanceof squel.cls.FromTableBlock)
+          assert.same _.extend({}, expectedOptions, { prefix: 'FROM'}), block.options
+        else if (block instanceof squel.cls.WhereBlock)
+          assert.same _.extend({}, expectedOptions, { verb: 'WHERE'}), block.options
+        else if (block instanceof squel.cls.HavingBlock)
+          assert.same _.extend({}, expectedOptions, { verb: 'HAVING'}), block.options
+        else
+          assert.same expectedOptions, block.options
+
+    'override blocks': ->
+      block = new squel.cls.StringBlock('SELECT')
+      @inst = @func {}, [block]
+      assert.same [block], @inst.blocks
+
+  'build query':
+    'no need to call from() first': ->
+      @inst.toString()
+
+    '>> function(1)':
+      beforeEach: -> @inst.function('1')
+      toString: ->
+        assert.same @inst.toString(), 'SELECT 1'
+      toParam: ->
+        assert.same @inst.toParam(), { text: 'SELECT 1', values: [] }
+
+    '>> function(MAX(?,?), 3, 5)':
+      beforeEach: -> @inst.function('MAX(?, ?)', 3, 5)
+      toString: ->
+        assert.same @inst.toString(), 'SELECT MAX(3, 5)'
+      toParam: ->
+        assert.same @inst.toParam(), { text: 'SELECT MAX(?, ?)', values: [3, 5] }
+
+    '>> from(table).from(table2, alias2)':
+      beforeEach: -> @inst.from('table').from('table2', 'alias2')
+      toString: ->
+        assert.same @inst.toString(), 'SELECT * FROM table, table2 `alias2`'
+
+      '>> field(squel.select().field("MAX(score)").FROM("scores"), fa1)':
+        beforeEach: -> @inst.field(squel.select().field("MAX(score)").from("scores"), 'fa1')
+        toString: ->
+          assert.same @inst.toString(), 'SELECT (SELECT MAX(score) FROM scores) AS "fa1" FROM table, table2 `alias2`'
+
+      '>> field(squel.case().when(score > ?, 1).then(1), fa1)':
+        beforeEach: -> @inst.field(squel.case().when("score > ?", 1).then(1), 'fa1')
+        toString: ->
+          assert.same @inst.toString(), 'SELECT CASE WHEN (score > 1) THEN 1 ELSE NULL END AS "fa1" FROM table, table2 `alias2`'
+        toParam: ->
+          assert.same @inst.toParam(), { text: 'SELECT CASE WHEN (score > ?) THEN 1 ELSE NULL END AS "fa1" FROM table, table2 `alias2`', values: [1] }
+
+      '>> field( squel.str(SUM(?), squel.case().when(score > ?, 1).then(1) ), fa1)':
+        beforeEach: -> @inst.field( squel.str('SUM(?)', squel.case().when("score > ?", 1).then(1)), 'fa1')
+        toString: ->
+          assert.same @inst.toString(), 'SELECT (SUM((CASE WHEN (score > 1) THEN 1 ELSE NULL END))) AS "fa1" FROM table, table2 `alias2`'
+        toParam: ->
+          assert.same @inst.toParam(), { text: 'SELECT (SUM(CASE WHEN (score > ?) THEN 1 ELSE NULL END)) AS "fa1" FROM table, table2 `alias2`', values: [1] }
+
+      '>> field(field1, fa1) >> field(field2)':
+        beforeEach: -> @inst.field('field1', 'fa1').field('field2')
+        toString: ->
+          assert.same @inst.toString(), 'SELECT field1 AS "fa1", field2 FROM table, table2 `alias2`'
+
+        '>> distinct()':
+          beforeEach: -> @inst.distinct()
+          toString: ->
+            assert.same @inst.toString(), 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2`'
+
+          '>> group(field) >> group(field2)':
+            beforeEach: -> @inst.group('field').group('field2')
+            toString: ->
+              assert.same @inst.toString(), 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` GROUP BY field, field2'
+
+            '>> where(a = ?, squel.select().field("MAX(score)").from("scores"))':
+              beforeEach: ->
+                @subQuery = squel.select().field("MAX(score)").from("scores")
+                @inst.where('a = ?', @subQuery)
+              toString: ->
+                assert.same @inst.toString(), 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` WHERE (a = (SELECT MAX(score) FROM scores)) GROUP BY field, field2'
+              toParam: ->
+                assert.same @inst.toParam(), {
+                  text: 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` WHERE (a = (SELECT MAX(score) FROM scores)) GROUP BY field, field2'
+                  values: []
+                }
+
+            '>> where(squel.expr().and(a = ?, 1).and( expr().or(b = ?, 2).or(c = ?, 3) ))':
+              beforeEach: -> @inst.where(squel.expr().and("a = ?", 1).and(squel.expr().or("b = ?", 2).or("c = ?", 3)))
+              toString: ->
+                assert.same @inst.toString(), 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` WHERE (a = 1 AND (b = 2 OR c = 3)) GROUP BY field, field2'
+              toParam: ->
+                assert.same @inst.toParam(), {
+                  text: 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` WHERE (a = ? AND (b = ? OR c = ?)) GROUP BY field, field2'
+                  values: [1, 2, 3]
+                }
+
+            '>> where(squel.expr().and(a = ?, QueryBuilder).and( expr().or(b = ?, 2).or(c = ?, 3) ))':
+              beforeEach: ->
+                subQuery = squel.select().field('field1').from('table1').where('field2 = ?', 10)
+                @inst.where(squel.expr().and("a = ?", subQuery).and(squel.expr().or("b = ?", 2).or("c = ?", 3)))
+              toString: ->
+                assert.same @inst.toString(), 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` WHERE (a = (SELECT field1 FROM table1 WHERE (field2 = 10)) AND (b = 2 OR c = 3)) GROUP BY field, field2'
+              toParam: ->
+                assert.same @inst.toParam(), {
+                  text: 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` WHERE (a = (SELECT field1 FROM table1 WHERE (field2 = ?)) AND (b = ? OR c = ?)) GROUP BY field, field2'
+                  values: [10, 2, 3]
+                }
+
+            '>> having(squel.expr().and(a = ?, QueryBuilder).and( expr().or(b = ?, 2).or(c = ?, 3) ))':
+              beforeEach: ->
+                subQuery = squel.select().field('field1').from('table1').having('field2 = ?', 10)
+                @inst.having(squel.expr().and("a = ?", subQuery).and(squel.expr().or("b = ?", 2).or("c = ?", 3)))
+              toString: ->
+                assert.same @inst.toString(), 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` GROUP BY field, field2 HAVING (a = (SELECT field1 FROM table1 HAVING (field2 = 10)) AND (b = 2 OR c = 3))'
+              toParam: ->
+                assert.same @inst.toParam(), {
+                  text: 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` GROUP BY field, field2 HAVING (a = (SELECT field1 FROM table1 HAVING (field2 = ?)) AND (b = ? OR c = ?))'
+                  values: [10, 2, 3]
+                }
+
+            '>> where(a = ?, null)':
+              beforeEach: -> @inst.where('a = ?', null)
+              toString: ->
+                assert.same @inst.toString(), 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` WHERE (a = NULL) GROUP BY field, field2'
+              toParam: ->
+                assert.same @inst.toParam(), {
+                  text: 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` WHERE (a = ?) GROUP BY field, field2'
+                  values: [null]
+                }
+                
+            '>> where(a = ?, 1)':
+              beforeEach: -> @inst.where('a = ?', 1)
+              toString: ->
+                assert.same @inst.toString(), 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` WHERE (a = 1) GROUP BY field, field2'
+              toParam: ->
+                assert.same @inst.toParam(), {
+                  text: 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` WHERE (a = ?) GROUP BY field, field2'
+                  values: [1]
+                }
+
+              '>> join(other_table)':
+                beforeEach: -> @inst.join('other_table')
+                toString: ->
+                  assert.same @inst.toString(), 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` INNER JOIN other_table WHERE (a = 1) GROUP BY field, field2'
+
+                '>> order(a)':
+                  beforeEach: -> @inst.order('a')
+                  toString: ->
+                    assert.same @inst.toString(), 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` INNER JOIN other_table WHERE (a = 1) GROUP BY field, field2 ORDER BY a ASC'
+
+                '>> order(a, null)':
+                  beforeEach: -> @inst.order('a', null)
+                  toString: ->
+                    assert.same @inst.toString(), 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` INNER JOIN other_table WHERE (a = 1) GROUP BY field, field2 ORDER BY a'
+
+                '>> order(a, true)':
+                  beforeEach: -> @inst.order('a', true)
+                  toString: ->
+                    assert.same @inst.toString(), 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` INNER JOIN other_table WHERE (a = 1) GROUP BY field, field2 ORDER BY a ASC'
+
+                  '>> limit(2)':
+                    beforeEach: -> @inst.limit(2)
+                    toString: ->
+                      assert.same @inst.toString(), 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` INNER JOIN other_table WHERE (a = 1) GROUP BY field, field2 ORDER BY a ASC LIMIT 2'
+
+                    '>> offset(3)':
+                      beforeEach: -> @inst.offset(3)
+                      toString: ->
+                        assert.same @inst.toString(), 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` INNER JOIN other_table WHERE (a = 1) GROUP BY field, field2 ORDER BY a ASC LIMIT 2 OFFSET 3'
+
+                '>> order(DIST(?,?), true, 2, 3)':
+                  beforeEach: -> @inst.order('DIST(?, ?)', true, 2, false)
+                  toString: ->
+                    assert.same @inst.toString(), 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` INNER JOIN other_table WHERE (a = 1) GROUP BY field, field2 ORDER BY DIST(2, FALSE) ASC'
+                  toParam: ->
+                    assert.same @inst.toParam(), {
+                      text: 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` INNER JOIN other_table WHERE (a = ?) GROUP BY field, field2 ORDER BY DIST(?, ?) ASC'
+                      values: [1, 2, false]
+                    }
+
+                '>> order(a)':
+                  beforeEach: -> @inst.order('a')
+                  toString: ->
+                    assert.same @inst.toString(), 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` INNER JOIN other_table WHERE (a = 1) GROUP BY field, field2 ORDER BY a ASC'
+
+                '>> order(b, null)':
+                  beforeEach: -> @inst.order('b', null)
+                  toString: ->
+                    assert.same @inst.toString(), 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` INNER JOIN other_table WHERE (a = 1) GROUP BY field, field2 ORDER BY b'
+
+              '>> join(other_table, condition = expr())':
+                beforeEach: ->
+                  subQuery = squel.select().field('abc').from('table1').where('adf = ?', 'today1')
+                  subQuery2 = squel.select().field('xyz').from('table2').where('adf = ?', 'today2')
+                  expr = squel.expr().and('field1 = ?', subQuery)
+                  @inst.join('other_table', null, expr)
+                  @inst.where('def IN ?', subQuery2)
+                toString: ->
+                  assert.same @inst.toString(), "SELECT DISTINCT field1 AS \"fa1\", field2 FROM table, table2 `alias2` INNER JOIN other_table ON (field1 = (SELECT abc FROM table1 WHERE (adf = 'today1'))) WHERE (a = 1) AND (def IN (SELECT xyz FROM table2 WHERE (adf = 'today2'))) GROUP BY field, field2"
+                toParam: ->
+                  assert.same @inst.toParam(), { text: 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` INNER JOIN other_table ON (field1 = (SELECT abc FROM table1 WHERE (adf = ?))) WHERE (a = ?) AND (def IN (SELECT xyz FROM table2 WHERE (adf = ?))) GROUP BY field, field2', values: ["today1",1,"today2"] }
+
+
+    'nested queries':
+      'basic': ->
+        inner1 = squel.select().from('students')
+        inner2 = squel.select().from('scores')
+
+        @inst.from(inner1).from(inner2, 'scores')
+
+        assert.same @inst.toString(), "SELECT * FROM (SELECT * FROM students), (SELECT * FROM scores) `scores`"
+      'deep nesting': ->
+        inner1 = squel.select().from('students')
+        inner2 = squel.select().from(inner1)
+
+        @inst.from(inner2)
+
+        assert.same @inst.toString(), "SELECT * FROM (SELECT * FROM (SELECT * FROM students))"
+
+      'nesting in JOINs': ->
+        inner1 = squel.select().from('students')
+        inner2 = squel.select().from(inner1)
+
+        @inst.from('schools').join(inner2, 'meh', 'meh.ID = ID')
+
+        assert.same @inst.toString(), "SELECT * FROM schools INNER JOIN (SELECT * FROM (SELECT * FROM students)) `meh` ON (meh.ID = ID)"
+
+      'nesting in JOINs with params': ->
+        inner1 = squel.select().from('students').where('age = ?', 6)
+        inner2 = squel.select().from(inner1)
+
+        @inst.from('schools').where('school_type = ?', 'junior').join(inner2, 'meh', 'meh.ID = ID')
+
+        assert.same @inst.toString(), "SELECT * FROM schools INNER JOIN (SELECT * FROM (SELECT * FROM students WHERE (age = 6))) `meh` ON (meh.ID = ID) WHERE (school_type = 'junior')"
+        assert.same @inst.toParam(), { "text": "SELECT * FROM schools INNER JOIN (SELECT * FROM (SELECT * FROM students WHERE (age = ?))) `meh` ON (meh.ID = ID) WHERE (school_type = ?)", "values": [6,'junior'] }
+        assert.same @inst.toParam({ "numberedParameters": true}), { "text": "SELECT * FROM schools INNER JOIN (SELECT * FROM (SELECT * FROM students WHERE (age = $1))) `meh` ON (meh.ID = ID) WHERE (school_type = $2)", "values": [6,'junior'] }
+
+  'Complex table name, e.g. LATERAL (#230)':
+    beforeEach: ->
+      @inst = squel.select().from('foo').from(squel.str('LATERAL(?)', squel.select().from('bar').where('bar.id = ?', 2)), 'ss')
+    'toString': ->
+      assert.same @inst.toString(), 'SELECT * FROM foo, (LATERAL((SELECT * FROM bar WHERE (bar.id = 2)))) `ss`', 
+    'toParam': ->
+      assert.same @inst.toParam(), {
+        text: 'SELECT * FROM foo, (LATERAL((SELECT * FROM bar WHERE (bar.id = ?)))) `ss`'
+        values: [2]
+      }
+
+  'cloning':
+    'basic': ->
+      newinst = @inst.from('students').limit(10).clone()
+      newinst.limit(20)
+
+      assert.same 'SELECT * FROM students LIMIT 10', @inst.toString()
+      assert.same 'SELECT * FROM students LIMIT 20', newinst.toString()
+
+    'with expressions (ticket #120)': ->
+      expr = squel.expr().and('a = 1')
+      newinst = @inst.from('table').left_join('table_2', 't', expr)
+        .clone()
+        .where('c = 1')
+      
+      expr.and('b = 2')
+
+      assert.same 'SELECT * FROM table LEFT JOIN table_2 `t` ON (a = 1 AND b = 2)', @inst.toString()
+      assert.same 'SELECT * FROM table LEFT JOIN table_2 `t` ON (a = 1) WHERE (c = 1)', newinst.toString()
+
+    'with sub-queries (ticket #120)': ->
+      newinst = @inst.from(squel.select().from('students')).limit(30)
+        .clone()
+        .where('c = 1')
+        .limit(35)
+      
+      assert.same 'SELECT * FROM (SELECT * FROM students) LIMIT 30', @inst.toString()
+      assert.same 'SELECT * FROM (SELECT * FROM students) WHERE (c = 1) LIMIT 35', newinst.toString()
+
+    'with complex expressions': ->
+      expr = squel.expr().and(
+        squel.expr().or('b = 2').or(
+          squel.expr().and('c = 3').and('d = 4')
+        )
+      ).and('a = 1')
+
+      newinst = @inst.from('table').left_join('table_2', 't', expr)
+        .clone()
+        .where('c = 1')
+      
+      expr.and('e = 5')
+
+      assert.same @inst.toString(), 'SELECT * FROM table LEFT JOIN table_2 `t` ON ((b = 2 OR (c = 3 AND d = 4)) AND a = 1 AND e = 5)'
+      assert.same newinst.toString(), 'SELECT * FROM table LEFT JOIN table_2 `t` ON ((b = 2 OR (c = 3 AND d = 4)) AND a = 1) WHERE (c = 1)'
+
+
+
+  'can specify block separator': ->
+    assert.same( squel.select({separator: '\n'})
+      .field('thing')
+      .from('table')
+      .toString(), """
+        SELECT
+        thing
+        FROM table
+      """
+    )
+
+  '#242 - auto-quote table names':
+    beforeEach: ->
+      @inst = squel
+        .select({ autoQuoteTableNames: true })
+        .field('name')
+        .where('age > ?', 15)      
+
+    'using string': 
+      beforeEach: ->
+        @inst.from('students', 's')
+
+      toString: ->
+        assert.same @inst.toString(), """
+        SELECT name FROM `students` `s` WHERE (age > 15)
+        """
+
+      toParam: ->
+        assert.same @inst.toParam(), {
+          "text": "SELECT name FROM `students` `s` WHERE (age > ?)"
+          "values": [15]
+        }
+
+    'using query builder': 
+      beforeEach: ->
+        @inst.from(squel.select().from('students'), 's')
+
+      toString: ->
+        assert.same @inst.toString(), """
+        SELECT name FROM (SELECT * FROM students) `s` WHERE (age > 15)
+        """
+
+      toParam: ->
+        assert.same @inst.toParam(), {
+          "text": "SELECT name FROM (SELECT * FROM students) `s` WHERE (age > ?)"
+          "values": [15]
+        }
+
+
+  'UNION JOINs':
+    'Two Queries NO Params':
+      beforeEach: ->
+        @qry1 = squel.select().field('name').from('students').where('age > 15')
+        @qry2 = squel.select().field('name').from('students').where('age < 6')
+        @qry1.union(@qry2)
+
+      toString: ->
+        assert.same @qry1.toString(), """
+        SELECT name FROM students WHERE (age > 15) UNION (SELECT name FROM students WHERE (age < 6))
+        """
+      toParam: ->
+        assert.same @qry1.toParam(), {
+          "text": "SELECT name FROM students WHERE (age > 15) UNION (SELECT name FROM students WHERE (age < 6))"
+          "values": [
+          ]
+        }
+
+    'Two Queries with Params':
+      beforeEach: ->
+        @qry1 = squel.select().field('name').from('students').where('age > ?', 15)
+        @qry2 = squel.select().field('name').from('students').where('age < ?', 6)
+        @qry1.union(@qry2)
+
+      toString: ->
+        assert.same @qry1.toString(), """
+        SELECT name FROM students WHERE (age > 15) UNION (SELECT name FROM students WHERE (age < 6))
+        """
+      toParam: ->
+        assert.same @qry1.toParam(), {
+          "text": "SELECT name FROM students WHERE (age > ?) UNION (SELECT name FROM students WHERE (age < ?))"
+          "values": [
+            15
+            6
+          ]
+        }
+
+    'Three Queries':
+      beforeEach: ->
+        @qry1 = squel.select().field('name').from('students').where('age > ?', 15)
+        @qry2 = squel.select().field('name').from('students').where('age < 6')
+        @qry3 = squel.select().field('name').from('students').where('age = ?', 8)
+        @qry1.union(@qry2)
+        @qry1.union(@qry3)
+
+      toParam: ->
+        assert.same @qry1.toParam(), {
+          "text": "SELECT name FROM students WHERE (age > ?) UNION (SELECT name FROM students WHERE (age < 6)) UNION (SELECT name FROM students WHERE (age = ?))"
+          "values": [
+            15
+            8
+          ]
+        }
+      'toParam(2)': ->
+        assert.same @qry1.toParam({ "numberedParameters": true, "numberedParametersStartAt": 2}), {
+          "text": "SELECT name FROM students WHERE (age > $2) UNION (SELECT name FROM students WHERE (age < 6)) UNION (SELECT name FROM students WHERE (age = $3))"
+          "values": [
+            15
+            8
+          ]
+        }
+
+    'Multi-Parameter Query':
+      beforeEach: ->
+        @qry1 = squel.select().field('name').from('students').where('age > ?', 15)
+        @qry2 = squel.select().field('name').from('students').where('age < ?', 6)
+        @qry3 = squel.select().field('name').from('students').where('age = ?', 8)
+        @qry4 = squel.select().field('name').from('students').where('age IN [?, ?]', 2, 10)
+        @qry1.union(@qry2)
+        @qry1.union(@qry3)
+        @qry4.union_all(@qry1)
+
+      toString: ->
+        assert.same @qry4.toString(), """
+        SELECT name FROM students WHERE (age IN [2, 10]) UNION ALL (SELECT name FROM students WHERE (age > 15) UNION (SELECT name FROM students WHERE (age < 6)) UNION (SELECT name FROM students WHERE (age = 8)))
+        """
+      toParam: ->
+        assert.same @qry4.toParam({ "numberedParameters": true}), {
+          "text": "SELECT name FROM students WHERE (age IN [$1, $2]) UNION ALL (SELECT name FROM students WHERE (age > $3) UNION (SELECT name FROM students WHERE (age < $4)) UNION (SELECT name FROM students WHERE (age = $5)))"
+          "values": [
+            2
+            10
+            15
+            6
+            8
+          ]
+        }
+
+
+module?.exports[require('path').basename(__filename)] = test

+ 65 - 0
www/lib/squel/test/testbase.coffee

@@ -0,0 +1,65 @@
+###
+Copyright (c) 2014 Ramesh Nair (hiddentao.com)
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+###
+
+
+sinon = require('sinon')
+chai = require("chai")
+
+assert = chai.assert
+expect = chai.expect
+should = chai.should()
+
+
+###
+Assert that two items are the same.
+
+@param actual
+@param expected
+@param {String} [message] failure message
+###
+assert.same = (actual, expected, message) ->
+  assert.deepEqual(actual, expected, message)
+
+
+
+
+testCreator = ->
+  test =
+    mocker: null
+    beforeEach: ->
+      test.mocker = sinon.sandbox.create()
+    afterEach: ->
+      test.mocker.restore()
+
+  test
+
+
+module?.exports =
+  _: require('underscore')
+  testCreator: testCreator
+  assert: assert
+  expect: expect
+  should: should
+

+ 284 - 0
www/lib/squel/test/update.test.coffee

@@ -0,0 +1,284 @@
+###
+Copyright (c) 2014 Ramesh Nair (hiddentao.com)
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+###
+
+
+squel = require "../dist/squel-basic"
+{_, testCreator, assert, expect, should} = require './testbase'
+test = testCreator()
+
+
+
+test['UPDATE builder'] =
+  beforeEach: ->
+    @func = squel.update
+    @inst = @func()
+
+  'instanceof QueryBuilder': ->
+    assert.instanceOf @inst, squel.cls.QueryBuilder
+
+  'constructor':
+    'override options': ->
+      @inst = squel.update
+        usingValuePlaceholders: true
+        dummy: true
+
+      expectedOptions = _.extend {}, squel.cls.DefaultQueryBuilderOptions,
+        usingValuePlaceholders: true
+        dummy: true
+
+      for block in @inst.blocks
+        if (block instanceof squel.cls.WhereBlock)
+          assert.same _.extend({}, expectedOptions, { verb: 'WHERE'}), block.options
+        else
+          assert.same expectedOptions, block.options
+
+    'override blocks': ->
+      block = new squel.cls.StringBlock('SELECT')
+      @inst = @func {}, [block]
+      assert.same [block], @inst.blocks
+
+
+  'build query':
+    'need to call set() first': ->
+      @inst.table('table')
+      assert.throws (=> @inst.toString()), 'set() needs to be called'
+
+    '>> table(table, t1).set(field, 1)':
+      beforeEach: -> @inst.table('table', 't1').set('field', 1)
+      toString: ->
+        assert.same @inst.toString(), 'UPDATE table `t1` SET field = 1'
+
+      '>> set(field2, 1.2)':
+        beforeEach: -> @inst.set('field2', 1.2)
+        toString: ->
+          assert.same @inst.toString(), 'UPDATE table `t1` SET field = 1, field2 = 1.2'
+
+      '>> set(field2, true)':
+        beforeEach: -> @inst.set('field2', true)
+        toString: ->
+          assert.same @inst.toString(), 'UPDATE table `t1` SET field = 1, field2 = TRUE'
+
+      '>> set(field2, null)':
+        beforeEach: -> @inst.set('field2', null)
+        toString: ->
+          assert.same @inst.toString(), 'UPDATE table `t1` SET field = 1, field2 = NULL'
+        toParam: ->
+          assert.same @inst.toParam(), { text: 'UPDATE table `t1` SET field = 1, field2 = ?', values: [null] }
+
+      '>> set(field2, "str")':
+        beforeEach: -> @inst.set('field2', 'str')
+        toString: ->
+          assert.same @inst.toString(), 'UPDATE table `t1` SET field = 1, field2 = \'str\''
+        toParam: ->
+          assert.same @inst.toParam(), {
+            text: 'UPDATE table `t1` SET field = ?, field2 = ?'
+            values: [1, 'str']
+          }
+
+      '>> set(field2, "str", { dontQuote: true })':
+        beforeEach: -> @inst.set('field2', 'str', dontQuote: true)
+        toString: ->
+          assert.same @inst.toString(), 'UPDATE table `t1` SET field = 1, field2 = str'
+        toParam: ->
+          assert.same @inst.toParam(), {
+            text: 'UPDATE table `t1` SET field = ?, field2 = ?'
+            values: [1, 'str']
+          }
+
+      '>> set(field, query builder)':
+        beforeEach: ->
+          @subQuery = squel.select().field('MAX(score)').from('scores')
+          @inst.set( 'field',  @subQuery )
+        toString: ->
+          assert.same @inst.toString(), 'UPDATE table `t1` SET field = (SELECT MAX(score) FROM scores)'
+        toParam: ->
+          parameterized = @inst.toParam()
+          assert.same parameterized.text, 'UPDATE table `t1` SET field = (SELECT MAX(score) FROM scores)'
+          assert.same parameterized.values, []
+
+      '>> set(custom value type)':
+        beforeEach: ->
+          class MyClass
+          @inst.registerValueHandler MyClass, (a) -> 'abcd'
+          @inst.set( 'field',  new MyClass() )
+        toString: ->
+          assert.same @inst.toString(), 'UPDATE table `t1` SET field = (abcd)'
+        toParam: ->
+          parameterized = @inst.toParam()
+          assert.same parameterized.text, 'UPDATE table `t1` SET field = ?'
+          assert.same parameterized.values, ['abcd']
+
+      '>> setFields({field2: \'value2\', field3: true })':
+        beforeEach: -> @inst.setFields({field2: 'value2', field3: true })
+        toString: ->
+          assert.same @inst.toString(), 'UPDATE table `t1` SET field = 1, field2 = \'value2\', field3 = TRUE'
+        toParam: ->
+          parameterized = @inst.toParam()
+          assert.same parameterized.text, 'UPDATE table `t1` SET field = ?, field2 = ?, field3 = ?'
+          assert.same parameterized.values, [1, 'value2', true]
+
+      '>> setFields({field2: \'value2\', field: true })':
+        beforeEach: -> @inst.setFields({field2: 'value2', field: true })
+        toString: ->
+          assert.same @inst.toString(), 'UPDATE table `t1` SET field = TRUE, field2 = \'value2\''
+
+      '>> set(field2, null)':
+        beforeEach: -> @inst.set('field2', null)
+        toString: ->
+          assert.same @inst.toString(), 'UPDATE table `t1` SET field = 1, field2 = NULL'
+
+        '>> table(table2)':
+          beforeEach: -> @inst.table('table2')
+          toString: ->
+            assert.same @inst.toString(), 'UPDATE table `t1`, table2 SET field = 1, field2 = NULL'
+
+          '>> where(a = 1)':
+            beforeEach: -> @inst.where('a = 1')
+            toString: ->
+              assert.same @inst.toString(), 'UPDATE table `t1`, table2 SET field = 1, field2 = NULL WHERE (a = 1)'
+
+            '>> order(a, true)':
+              beforeEach: -> @inst.order('a', true)
+              toString: ->
+                assert.same @inst.toString(), 'UPDATE table `t1`, table2 SET field = 1, field2 = NULL WHERE (a = 1) ORDER BY a ASC'
+
+              '>> limit(2)':
+                beforeEach: -> @inst.limit(2)
+                toString: ->
+                  assert.same @inst.toString(), 'UPDATE table `t1`, table2 SET field = 1, field2 = NULL WHERE (a = 1) ORDER BY a ASC LIMIT 2'
+
+    '>> table(table, t1).setFields({field1: 1, field2: \'value2\'})':
+      beforeEach: -> @inst.table('table', 't1').setFields({field1: 1, field2: 'value2' })
+      toString: ->
+        assert.same @inst.toString(), 'UPDATE table `t1` SET field1 = 1, field2 = \'value2\''
+
+      '>> set(field1, 1.2)':
+        beforeEach: -> @inst.set('field1', 1.2)
+        toString: ->
+          assert.same @inst.toString(), 'UPDATE table `t1` SET field1 = 1.2, field2 = \'value2\''
+
+      '>> setFields({field3: true, field4: \'value4\'})':
+        beforeEach: -> @inst.setFields({field3: true, field4: 'value4'})
+        toString: ->
+          assert.same @inst.toString(), 'UPDATE table `t1` SET field1 = 1, field2 = \'value2\', field3 = TRUE, field4 = \'value4\''
+
+      '>> setFields({field1: true, field3: \'value3\'})':
+        beforeEach: -> @inst.setFields({field1: true, field3: 'value3'})
+        toString: ->
+          assert.same @inst.toString(), 'UPDATE table `t1` SET field1 = TRUE, field2 = \'value2\', field3 = \'value3\''
+
+    '>> table(table, t1).set("count = count + 1")':
+      beforeEach: -> @inst.table('table', 't1').set('count = count + 1')
+      toString: ->
+        assert.same @inst.toString(), 'UPDATE table `t1` SET count = count + 1'
+
+  'str()':
+    beforeEach: -> @inst.table('students').set('field', squel.str('GETDATE(?, ?)', 2014, '"feb"'))
+    toString: ->
+      assert.same 'UPDATE students SET field = (GETDATE(2014, \'"feb"\'))', @inst.toString()
+    toParam: ->  
+      assert.same { text: 'UPDATE students SET field = (GETDATE(?, ?))', values: [2014, '"feb"'] }, @inst.toParam()
+
+  'string formatting':
+    beforeEach: -> 
+      @inst.updateOptions {
+        stringFormatter: (str) -> "N'#{str}'"
+      }
+      @inst.table('students').set('field', 'jack')
+    toString: ->
+      assert.same 'UPDATE students SET field = N\'jack\'', @inst.toString()
+    toParam: ->  
+      assert.same { text: 'UPDATE students SET field = ?', values: ["jack"] }, @inst.toParam()
+
+  'fix for hiddentao/squel#63': ->
+    newinst = @inst.table('students').set('field = field + 1')
+    newinst.set('field2', 2).set('field3', true)
+    assert.same { text: 'UPDATE students SET field = field + 1, field2 = ?, field3 = ?', values: [2, true] }, @inst.toParam()
+
+  'dontQuote and replaceSingleQuotes set(field2, "ISNULL(\'str\', str)", { dontQuote: true })':
+    beforeEach: ->
+      @inst = squel.update replaceSingleQuotes: true
+      @inst.table('table', 't1').set('field', 1)
+      @inst.set('field2', "ISNULL('str', str)", dontQuote: true)
+    toString: ->
+      assert.same @inst.toString(), 'UPDATE table `t1` SET field = 1, field2 = ISNULL(\'str\', str)'
+    toParam: ->
+      assert.same @inst.toParam(), {
+        text: 'UPDATE table `t1` SET field = ?, field2 = ?'
+        values: [1, "ISNULL('str', str)"]
+      }
+
+  'fix for #223 - careful about array looping methods':
+    beforeEach: ->
+      Array::substr = () -> 1
+    afterEach: ->
+      delete Array::substr;
+    check: ->
+      @inst = squel.update()
+        .table('users')
+        .where('id = ?', 123)
+        .set('active', 1)
+        .set('regular', 0)
+        .set('moderator',1)
+
+      assert.same @inst.toParam(), {
+        text: 'UPDATE users SET active = ?, regular = ?, moderator = ? WHERE (id = ?)',
+        values: [1, 0, 1, 123],
+      }
+
+  'fix for #225 - autoquoting field names': ->
+    @inst = squel.update(autoQuoteFieldNames: true)
+      .table('users')
+      .where('id = ?', 123)
+      .set('active', 1)
+      .set('regular', 0)
+      .set('moderator',1)
+
+      assert.same @inst.toParam(), {
+        text: 'UPDATE users SET `active` = ?, `regular` = ?, `moderator` = ? WHERE (id = ?)',
+        values: [1, 0, 1, 123],
+      }
+
+  'fix for #243 - ampersand in conditions':
+    beforeEach: ->
+      @inst = squel.update().table('a').set('a = a & ?', 2)
+    toString: ->
+      assert.same @inst.toString(), 'UPDATE a SET a = a & 2'
+    toParam: ->
+      assert.same @inst.toParam(), {
+        text: 'UPDATE a SET a = a & ?',
+        values: [2],
+      }
+
+  'cloning': ->
+    newinst = @inst.table('students').set('field', 1).clone()
+    newinst.set('field', 2).set('field2', true)
+
+    assert.same 'UPDATE students SET field = 1', @inst.toString()
+    assert.same 'UPDATE students SET field = 2, field2 = TRUE', newinst.toString()
+
+
+
+module?.exports[require('path').basename(__filename)] = test

Vissa filer visades inte eftersom för många filer har ändrats